Add argument 'mounts' to container
Introduce an option for users to bind-mount Cinder volumes to containers. The syntax will look like: $ zun run --mount source=my-volume,destination=/data <image> The above command will bind-mount cinder volume with name 'my-volume' into path '/data' inside the container. Change-Id: If8e44527e65cf574400b9043108e75b473b3627c Partial-Implements: blueprint direct-cinder-integration
This commit is contained in:
parent
3a3f4535b9
commit
ec873e01a3
@ -30,7 +30,7 @@ if not LOG.handlers:
|
||||
|
||||
HEADER_NAME = "OpenStack-API-Version"
|
||||
SERVICE_TYPE = "container"
|
||||
DEFAULT_API_VERSION = '1.9'
|
||||
DEFAULT_API_VERSION = '1.11'
|
||||
|
||||
_SUBSTITUTIONS = {}
|
||||
|
||||
|
@ -198,6 +198,33 @@ def parse_command(command):
|
||||
return " ".join(output)
|
||||
|
||||
|
||||
def parse_mounts(mounts):
|
||||
err_msg = ("Invalid mounts argument '%s'. mounts arguments must be of "
|
||||
"the form --mount source=<volume>,destination=<path>.")
|
||||
parsed_mounts = []
|
||||
for mount in mounts:
|
||||
mount_info = {"source": "", "destination": ""}
|
||||
for mnt in mount.split(","):
|
||||
try:
|
||||
k, v = mnt.split("=", 1)
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
except ValueError:
|
||||
raise apiexec.CommandError(err_msg % mnt)
|
||||
if k in mount_info:
|
||||
if mount_info[k]:
|
||||
raise apiexec.CommandError(err_msg % mnt)
|
||||
mount_info[k] = v
|
||||
else:
|
||||
raise apiexec.CommandError(err_msg % mnt)
|
||||
|
||||
if not mount_info['source'] or not mount_info['destination']:
|
||||
raise apiexec.CommandError(err_msg % mnt)
|
||||
|
||||
parsed_mounts.append(mount_info)
|
||||
return parsed_mounts
|
||||
|
||||
|
||||
def parse_nets(ns):
|
||||
err_msg = ("Invalid nets argument '%s'. nets arguments must be of "
|
||||
"the form --nets <network=network, v4-fixed-ip=ip-addr,"
|
||||
|
@ -137,6 +137,13 @@ class CreateContainer(command.ShowOne):
|
||||
' port: attach container to the neutron port with this UUID. '
|
||||
'v4-fixed-ip: IPv4 fixed address for container. '
|
||||
'v6-fixed-ip: IPv6 fixed address for container.')
|
||||
parser.add_argument(
|
||||
'--mount',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='<mount>',
|
||||
help='A dictionary to configure volumes mounted inside the '
|
||||
'container.')
|
||||
parser.add_argument(
|
||||
'--rm',
|
||||
dest='auto_remove',
|
||||
@ -180,6 +187,7 @@ class CreateContainer(command.ShowOne):
|
||||
opts['interactive'] = True
|
||||
opts['hints'] = zun_utils.format_args(parsed_args.hint)
|
||||
opts['nets'] = zun_utils.parse_nets(parsed_args.net)
|
||||
opts['mounts'] = zun_utils.parse_mounts(parsed_args.mount)
|
||||
opts['runtime'] = parsed_args.runtime
|
||||
opts['hostname'] = parsed_args.hostname
|
||||
|
||||
@ -680,6 +688,13 @@ class RunContainer(command.ShowOne):
|
||||
' port: attach container to the neutron port with this UUID. '
|
||||
'v4-fixed-ip: IPv4 fixed address for container. '
|
||||
'v6-fixed-ip: IPv6 fixed address for container.')
|
||||
parser.add_argument(
|
||||
'--mount',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='<mount>',
|
||||
help='A dictionary to configure volumes mounted inside the '
|
||||
'container.')
|
||||
parser.add_argument(
|
||||
'--rm',
|
||||
dest='auto_remove',
|
||||
@ -723,6 +738,7 @@ class RunContainer(command.ShowOne):
|
||||
opts['interactive'] = True
|
||||
opts['hints'] = zun_utils.format_args(parsed_args.hint)
|
||||
opts['nets'] = zun_utils.parse_nets(parsed_args.net)
|
||||
opts['mounts'] = zun_utils.parse_mounts(parsed_args.mount)
|
||||
opts['runtime'] = parsed_args.runtime
|
||||
opts['hostname'] = parsed_args.hostname
|
||||
|
||||
|
@ -246,7 +246,7 @@ class ShellTest(utils.TestCase):
|
||||
project_domain_id='', project_domain_name='',
|
||||
user_domain_id='', user_domain_name='', profile=None,
|
||||
endpoint_override=None, insecure=False,
|
||||
version=api_versions.APIVersion('1.9'))
|
||||
version=api_versions.APIVersion('1.11'))
|
||||
|
||||
def test_main_option_region(self):
|
||||
self.make_env()
|
||||
@ -274,7 +274,7 @@ class ShellTest(utils.TestCase):
|
||||
project_domain_id='', project_domain_name='',
|
||||
user_domain_id='', user_domain_name='', profile=None,
|
||||
endpoint_override=None, insecure=False,
|
||||
version=api_versions.APIVersion('1.9'))
|
||||
version=api_versions.APIVersion('1.11'))
|
||||
|
||||
@mock.patch('zunclient.client.Client')
|
||||
def test_main_endpoint_internal(self, mock_client):
|
||||
@ -288,7 +288,7 @@ class ShellTest(utils.TestCase):
|
||||
project_domain_id='', project_domain_name='',
|
||||
user_domain_id='', user_domain_name='', profile=None,
|
||||
endpoint_override=None, insecure=False,
|
||||
version=api_versions.APIVersion('1.9'))
|
||||
version=api_versions.APIVersion('1.11'))
|
||||
|
||||
|
||||
class ShellTestKeystoneV3(ShellTest):
|
||||
@ -319,4 +319,4 @@ class ShellTestKeystoneV3(ShellTest):
|
||||
project_domain_id='', project_domain_name='Default',
|
||||
user_domain_id='', user_domain_name='Default',
|
||||
endpoint_override=None, insecure=False, profile=None,
|
||||
version=api_versions.APIVersion('1.9'))
|
||||
version=api_versions.APIVersion('1.11'))
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
import mock
|
||||
|
||||
from zunclient.common.apiclient import exceptions as apiexec
|
||||
from zunclient.common import utils as zun_utils
|
||||
from zunclient.common.websocketclient import exceptions
|
||||
from zunclient.tests.unit.v1 import shell_test_base
|
||||
@ -167,6 +168,39 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||
self._invalid_choice_error)
|
||||
self.assertFalse(mock_run.called)
|
||||
|
||||
@mock.patch('zunclient.v1.containers_shell._show_container')
|
||||
@mock.patch('zunclient.v1.containers.ContainerManager.run')
|
||||
def test_zun_container_run_with_mount(
|
||||
self, mock_run, mock_show_container):
|
||||
mock_run.return_value = 'container'
|
||||
self._test_arg_success(
|
||||
'run --mount source=s,destination=d x')
|
||||
mock_show_container.assert_called_once_with('container')
|
||||
|
||||
def test_zun_container_run_with_mount_invalid_format(self):
|
||||
self.assertRaisesRegex(
|
||||
apiexec.CommandError, 'Invalid mounts argument',
|
||||
self.shell,
|
||||
'run --mount source,destination=d x')
|
||||
|
||||
def test_zun_container_run_with_mount_missed_key(self):
|
||||
self.assertRaisesRegex(
|
||||
apiexec.CommandError, 'Invalid mounts argument',
|
||||
self.shell,
|
||||
'run --mount source=s x')
|
||||
|
||||
def test_zun_container_run_with_mount_duplicated_key(self):
|
||||
self.assertRaisesRegex(
|
||||
apiexec.CommandError, 'Invalid mounts argument',
|
||||
self.shell,
|
||||
'run --mount source=s,source=s,destination=d x')
|
||||
|
||||
def test_zun_container_run_with_mount_invalid_key(self):
|
||||
self.assertRaisesRegex(
|
||||
apiexec.CommandError, 'Invalid mounts argument',
|
||||
self.shell,
|
||||
'run --mount invalid=s,destination=d x')
|
||||
|
||||
@mock.patch('zunclient.v1.containers_shell._show_container')
|
||||
@mock.patch('zunclient.v1.containers.ContainerManager.run')
|
||||
def test_zun_container_run_success_with_hostname(
|
||||
|
@ -23,7 +23,7 @@ CREATION_ATTRIBUTES = ['name', 'image', 'command', 'cpu', 'memory',
|
||||
'environment', 'workdir', 'labels', 'image_pull_policy',
|
||||
'restart_policy', 'interactive', 'image_driver',
|
||||
'security_groups', 'hints', 'nets', 'auto_remove',
|
||||
'runtime', 'hostname']
|
||||
'runtime', 'hostname', 'mounts']
|
||||
|
||||
|
||||
class Container(base.Resource):
|
||||
|
@ -112,6 +112,12 @@ def _show_container(container):
|
||||
'port: attach container to the neutron port with this UUID. '
|
||||
'v4-fixed-ip: IPv4 fixed address for container. '
|
||||
'v6-fixed-ip: IPv6 fixed address for container.')
|
||||
@utils.arg('--mount',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='<mount>',
|
||||
help='A dictionary to configure volumes mounted inside the '
|
||||
'container.')
|
||||
@utils.arg('--runtime',
|
||||
metavar='<runtime>',
|
||||
choices=['runc'],
|
||||
@ -136,6 +142,7 @@ def do_create(cs, args):
|
||||
opts['image_driver'] = args.image_driver
|
||||
opts['hints'] = zun_utils.format_args(args.hint)
|
||||
opts['nets'] = zun_utils.parse_nets(args.net)
|
||||
opts['mounts'] = zun_utils.parse_mounts(args.mount)
|
||||
opts['runtime'] = args.runtime
|
||||
opts['hostname'] = args.hostname
|
||||
|
||||
@ -503,6 +510,12 @@ def do_kill(cs, args):
|
||||
'port: attach container to the neutron port with this UUID. '
|
||||
'v4-fixed-ip: IPv4 fixed address for container. '
|
||||
'v6-fixed-ip: IPv6 fixed address for container.')
|
||||
@utils.arg('--mount',
|
||||
action='append',
|
||||
default=[],
|
||||
metavar='<mount>',
|
||||
help='A dictionary to configure volumes mounted inside the '
|
||||
'container.')
|
||||
@utils.arg('--runtime',
|
||||
metavar='<runtime>',
|
||||
choices=['runc'],
|
||||
@ -527,6 +540,7 @@ def do_run(cs, args):
|
||||
opts['image_driver'] = args.image_driver
|
||||
opts['hints'] = zun_utils.format_args(args.hint)
|
||||
opts['nets'] = zun_utils.parse_nets(args.net)
|
||||
opts['mounts'] = zun_utils.parse_mounts(args.mount)
|
||||
opts['runtime'] = args.runtime
|
||||
opts['hostname'] = args.hostname
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user