diff --git a/zunclient/common/utils.py b/zunclient/common/utils.py index d0e3d9c3..80708b5c 100644 --- a/zunclient/common/utils.py +++ b/zunclient/common/utils.py @@ -16,6 +16,9 @@ import json +from oslo_utils import netutils + +from zunclient.common.apiclient import exceptions as apiexec from zunclient.common import cliutils as utils from zunclient import exceptions as exc from zunclient.i18n import _ @@ -175,3 +178,41 @@ def parse_command(command): c = '"' + c + '"' output.append(c) return " ".join(output) + + +def parse_nets(ns): + err_msg = ("Invalid nets argument '%s'. nets arguments must be of " + "the form --nets , " + "with only one of network, or port specified.") + nets = [] + for net_str in ns: + net_info = {"network": "", "v4-fixed-ip": "", "v6-fixed-ip": "", + "port": ""} + for kv_str in net_str.split(","): + try: + k, v = kv_str.split("=", 1) + k = k.strip() + v = v.strip() + except ValueError: + raise apiexec.CommandError(err_msg % net_str) + if k in net_info: + if net_info[k]: + raise apiexec.CommandError(err_msg % net_str) + net_info[k] = v + else: + raise apiexec.CommandError(err_msg % net_str) + + if net_info['v4-fixed-ip'] and not netutils.is_valid_ipv4( + net_info['v4-fixed-ip']): + raise apiexec.CommandError("Invalid ipv4 address.") + + if net_info['v6-fixed-ip'] and not netutils.is_valid_ipv6( + net_info['v6-fixed-ip']): + raise apiexec.CommandError("Invalid ipv6 address.") + + if bool(net_info['network']) == bool(net_info['port']): + raise apiexec.CommandError(err_msg % net_str) + + nets.append(net_info) + return nets diff --git a/zunclient/osc/v1/containers.py b/zunclient/osc/v1/containers.py index ae843c55..ca94aeed 100644 --- a/zunclient/osc/v1/containers.py +++ b/zunclient/osc/v1/containers.py @@ -125,6 +125,19 @@ class CreateContainer(command.ShowOne): help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') + parser.add_argument( + '--nets', + metavar='', + action='append', + default=[], + help='Create network enpoints for the container. ' + 'auto: do not specify the network, zun will automatically' + 'create one. ' + 'network: attach container to the specified neutron networks.' + ' 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.') return parser def take_action(self, parsed_args): @@ -149,6 +162,7 @@ class CreateContainer(command.ShowOne): if parsed_args.interactive: opts['interactive'] = True opts['hints'] = zun_utils.format_args(parsed_args.hint) + opts['nets'] = zun_utils.parse_nets(parsed_args.nets) opts = zun_utils.remove_null_parms(**opts) container = client.containers.create(**opts) @@ -606,6 +620,25 @@ class RunContainer(command.ShowOne): help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') + parser.add_argument( + '--nets', + metavar='[net]', + action='append', + default=[], + help='Networks that container will connect to.') + parser.add_argument( + '--nets', + metavar='', + action='append', + default=[], + help='Create network enpoints for the container. ' + 'auto: do not specify the network, zun will automatically' + 'create one. ' + 'network: attach container to the specified neutron networks.' + ' 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.') return parser def take_action(self, parsed_args): @@ -630,6 +663,7 @@ class RunContainer(command.ShowOne): if parsed_args.interactive: opts['interactive'] = True opts['hints'] = zun_utils.format_args(parsed_args.hint) + opts['nets'] = zun_utils.parse_nets(parsed_args.nets) opts = zun_utils.remove_null_parms(**opts) container = client.containers.run(**opts) diff --git a/zunclient/tests/unit/common/test_utils.py b/zunclient/tests/unit/common/test_utils.py index 46234521..1c7b2dac 100644 --- a/zunclient/tests/unit/common/test_utils.py +++ b/zunclient/tests/unit/common/test_utils.py @@ -208,3 +208,39 @@ class CliUtilsTest(test_utils.BaseTestCase): ('c', dict_out['c'])]) self.assertEqual(six.text_type(dict_exp), six.text_type(dict_act)) + + +class ParseNetsTest(test_utils.BaseTestCase): + + def test_no_nets(self): + nets = [] + result = utils.parse_nets(nets) + self.assertEqual([], result) + + def test_nets_with_network(self): + nets = [' network = 1234567 , v4-fixed-ip = 172.17.0.3 '] + result = utils.parse_nets(nets) + self.assertEqual([{'network': '1234567', 'v4-fixed-ip': '172.17.0.3', + 'port': '', 'v6-fixed-ip': ''}], result) + + def test_nets_with_port(self): + nets = ['port=1234567, v6-fixed-ip=2001:db8::2'] + result = utils.parse_nets(nets) + self.assertEqual([{'network': '', 'v4-fixed-ip': '', + 'port': '1234567', 'v6-fixed-ip': '2001:db8::2'}], + result) + + def test_nets_with_only_ip(self): + nets = ['v4-fixed-ip = 172.17.0.3'] + self.assertRaises(exc.CommandError, + utils.parse_nets, nets) + + def test_nets_with_both_network_port(self): + nets = ['port=1234567, network=2345678, v4-fixed-ip=172.17.0.3'] + self.assertRaises(exc.CommandError, + utils.parse_nets, nets) + + def test_nets_with_invalid_ip(self): + nets = ['network=1234567, v4-fixed-ip=23.555.567,789'] + self.assertRaises(exc.CommandError, + utils.parse_nets, nets) diff --git a/zunclient/v1/containers.py b/zunclient/v1/containers.py index a1d5430c..a065365e 100644 --- a/zunclient/v1/containers.py +++ b/zunclient/v1/containers.py @@ -22,7 +22,7 @@ from zunclient import exceptions CREATION_ATTRIBUTES = ['name', 'image', 'command', 'cpu', 'memory', 'environment', 'workdir', 'labels', 'image_pull_policy', 'restart_policy', 'interactive', 'image_driver', - 'security_groups', 'hints'] + 'security_groups', 'hints', 'nets'] class Container(base.Resource): diff --git a/zunclient/v1/containers_shell.py b/zunclient/v1/containers_shell.py index 01cdddd2..12fd0c99 100644 --- a/zunclient/v1/containers_shell.py +++ b/zunclient/v1/containers_shell.py @@ -97,6 +97,18 @@ def _show_container(container): help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') +@utils.arg('--nets', + action='append', + default=[], + metavar='', + help='Create network enpoints for the container. ' + 'auto: do not specify the network, zun will automatically' + 'create one. ' + 'network: attach container to the specified neturon networks. ' + '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.') def do_create(cs, args): """Create a container.""" opts = {} @@ -110,6 +122,8 @@ def do_create(cs, args): opts['image_pull_policy'] = args.image_pull_policy opts['image_driver'] = args.image_driver opts['hints'] = zun_utils.format_args(args.hint) + nets = zun_utils.parse_nets(args.nets) + opts['nets'] = nets if args.security_group: opts['security_groups'] = args.security_group @@ -443,6 +457,18 @@ def do_kill(cs, args): help='The key-value pair(s) for scheduler to select host. ' 'The format of this parameter is "key=value[,key=value]". ' 'May be used multiple times.') +@utils.arg('--nets', + action='append', + default=[], + metavar='', + help='Create network enpoints for the container. ' + 'auto: do not specify the network, zun will automatically' + 'create one. ' + 'network: attach container to the specified neutron networks. ' + '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.') def do_run(cs, args): """Run a command in a new container.""" opts = {} @@ -456,6 +482,8 @@ def do_run(cs, args): opts['image_pull_policy'] = args.image_pull_policy opts['image_driver'] = args.image_driver opts['hints'] = zun_utils.format_args(args.hint) + nets = zun_utils.parse_nets(args.nets) + opts['nets'] = nets if args.security_group: opts['security_groups'] = args.security_group