diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/common/commandmanager.py | 2 | ||||
| -rw-r--r-- | openstackclient/common/exceptions.py | 2 | ||||
| -rw-r--r-- | openstackclient/common/module.py | 20 | ||||
| -rw-r--r-- | openstackclient/common/utils.py | 2 | ||||
| -rw-r--r-- | openstackclient/compute/v2/service.py | 36 | ||||
| -rw-r--r-- | openstackclient/network/v2/floating_ip.py | 2 | ||||
| -rw-r--r-- | openstackclient/network/v2/network.py | 2 | ||||
| -rw-r--r-- | openstackclient/network/v2/port.py | 2 | ||||
| -rw-r--r-- | openstackclient/network/v2/router.py | 2 | ||||
| -rw-r--r-- | openstackclient/network/v2/security_group_rule.py | 2 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet.py | 228 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet_pool.py | 2 | ||||
| -rw-r--r-- | openstackclient/shell.py | 2 | ||||
| -rw-r--r-- | openstackclient/tests/common/test_module.py | 16 | ||||
| -rw-r--r-- | openstackclient/tests/common/test_parseractions.py | 12 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/fakes.py | 2 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/test_service.py | 26 | ||||
| -rw-r--r-- | openstackclient/tests/network/v2/fakes.py | 2 | ||||
| -rw-r--r-- | openstackclient/tests/network/v2/test_subnet.py | 336 |
19 files changed, 652 insertions, 46 deletions
diff --git a/openstackclient/common/commandmanager.py b/openstackclient/common/commandmanager.py index b809d63a..c190e33e 100644 --- a/openstackclient/common/commandmanager.py +++ b/openstackclient/common/commandmanager.py @@ -56,4 +56,4 @@ class CommandManager(cliff.commandmanager.CommandManager): ) group_list.append(cmd_name) return group_list - return self.commands.keys() + return list(self.commands.keys()) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 5f5f5ab1..ee0f7a11 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -122,7 +122,7 @@ def from_response(response, body): cls = _code_map.get(response.status, ClientException) if body: if hasattr(body, 'keys'): - error = body[body.keys()[0]] + error = body[list(body.keys())[0]] message = error.get('message') details = error.get('details') else: diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index a3dea5da..30c67c68 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -19,6 +19,7 @@ import six import sys from openstackclient.common import command +from openstackclient.common import utils class ListCommand(command.Lister): @@ -29,9 +30,24 @@ class ListCommand(command.Lister): def take_action(self, parsed_args): cm = self.app.command_manager groups = cm.get_command_groups() - + groups = sorted(groups) columns = ('Command Group', 'Commands') - return (columns, ((c, cm.get_command_names(group=c)) for c in groups)) + + commands = [] + for group in groups: + command_names = cm.get_command_names(group) + command_names = sorted(command_names) + + if command_names != []: + + # TODO(bapalm): Fix this when cliff properly supports + # handling the detection rather than using the hard-code below. + if parsed_args.formatter == 'table': + command_names = utils.format_list(command_names, "\n") + + commands.append((group, command_names)) + + return (columns, commands) class ListModule(command.ShowOne): diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 840da402..c6ed6a71 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -281,7 +281,7 @@ def get_client_class(api_name, version, version_map): client_path = version_map[str(version)] except (KeyError, ValueError): msg = "Invalid %s client version '%s'. must be one of: %s" % ( - (api_name, version, ', '.join(version_map.keys()))) + (api_name, version, ', '.join(list(version_map.keys())))) raise exceptions.UnsupportedVersion(msg) return importutils.import_class(client_path) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 1cc91711..89f5cad9 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -49,19 +49,37 @@ class ListService(command.Lister): "--service", metavar="<service>", help="Name of service") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - columns = ( - "Id", - "Binary", - "Host", - "Zone", - "Status", - "State", - "Updated At" - ) + if parsed_args.long: + columns = ( + "Id", + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ) + else: + columns = ( + "Id", + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ) data = compute_client.services.list(parsed_args.host, parsed_args.service) return (columns, diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index e0a65a48..16f2b574 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -18,7 +18,7 @@ from openstackclient.network import common def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 612a1775..074b2754 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -38,7 +38,7 @@ _formatters = { def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index b618a4b0..241699ae 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -36,7 +36,7 @@ _formatters = { def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 96aa55b2..39431111 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -42,7 +42,7 @@ _formatters = { def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 92f28cce..9309b326 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -27,7 +27,7 @@ def _format_security_group_rule_show(obj): def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index b514a88f..794c787f 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -12,9 +12,14 @@ # """Subnet action implementations""" +import copy + +from json.encoder import JSONEncoder from openstackclient.common import command +from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common def _format_allocation_pools(data): @@ -23,21 +28,236 @@ def _format_allocation_pools(data): return ','.join(pool_formatted) +def _format_host_routes(data): + try: + return '\n'.join([JSONEncoder().encode(route) for route in data]) + except (TypeError, KeyError): + return '' + + _formatters = { 'allocation_pools': _format_allocation_pools, 'dns_nameservers': utils.format_list, - 'host_routes': utils.format_list, + 'host_routes': _format_host_routes, } def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') return tuple(sorted(columns)) +def convert_entries_to_nexthop(entries): + # Change 'gateway' entry to 'nexthop' + changed_entries = copy.deepcopy(entries) + for entry in changed_entries: + entry['nexthop'] = entry['gateway'] + del entry['gateway'] + + return changed_entries + + +def convert_entries_to_gateway(entries): + # Change 'nexhop' entry to 'gateway' + changed_entries = copy.deepcopy(entries) + for entry in changed_entries: + entry['gateway'] = entry['nexthop'] + del entry['nexthop'] + + return changed_entries + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + client = client_manager.network + attrs['network_id'] = client.find_network(parsed_args.network, + ignore_missing=False).id + + if parsed_args.subnet_pool is not None: + subnet_pool = client.find_subnet_pool(parsed_args.subnet_pool, + ignore_missing=False) + attrs['subnetpool_id'] = subnet_pool.id + + if parsed_args.use_default_subnet_pool: + attrs['use_default_subnetpool'] = True + if parsed_args.gateway.lower() != 'auto': + if parsed_args.gateway.lower() == 'none': + attrs['gateway_ip'] = None + else: + attrs['gateway_ip'] = parsed_args.gateway + if parsed_args.prefix_length is not None: + attrs['prefixlen'] = parsed_args.prefix_length + if parsed_args.subnet_range is not None: + attrs['cidr'] = parsed_args.subnet_range + if parsed_args.ip_version is not None: + attrs['ip_version'] = parsed_args.ip_version + if parsed_args.ipv6_ra_mode is not None: + attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode + if parsed_args.ipv6_address_mode is not None: + attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode + if parsed_args.allocation_pools is not None: + attrs['allocation_pools'] = parsed_args.allocation_pools + if parsed_args.enable_dhcp is not None: + attrs['enable_dhcp'] = parsed_args.enable_dhcp + if parsed_args.dns_nameservers is not None: + attrs['dns_nameservers'] = parsed_args.dns_nameservers + if parsed_args.host_routes is not None: + # Change 'gateway' entry to 'nexthop' to match the API + attrs['host_routes'] = convert_entries_to_nexthop( + parsed_args.host_routes) + + return attrs + + +class CreateSubnet(command.ShowOne): + """Create a subnet""" + + def get_parser(self, prog_name): + parser = super(CreateSubnet, self).get_parser(prog_name) + parser.add_argument( + 'name', + help='New subnet name', + ) + parser.add_argument( + '--project', + metavar='<project>', + help="Owner's project (name or ID)", + ) + identity_common.add_project_domain_option_to_parser(parser) + subnet_pool_group = parser.add_mutually_exclusive_group() + subnet_pool_group.add_argument( + '--subnet-pool', + metavar='<subnet-pool>', + help='Subnet pool from which this subnet will obtain a CIDR ' + '(Name or ID)', + ) + subnet_pool_group.add_argument( + '--use-default-subnet-pool', + action='store_true', + help='Use default subnet pool for --ip-version', + ) + parser.add_argument( + '--prefix-length', + metavar='<prefix-length>', + help='Prefix length for subnet allocation from subnetpool', + ) + parser.add_argument( + '--subnet-range', + metavar='<subnet-range>', + help='Subnet range in CIDR notation ' + '(required if --subnet-pool is not specified, ' + 'optional otherwise)', + ) + parser.add_argument( + '--allocation-pool', + metavar='start=<ip-address>,end=<ip-address>', + dest='allocation_pools', + action=parseractions.MultiKeyValueAction, + required_keys=['start', 'end'], + help='Allocation pool IP addresses for this subnet ' + 'e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(This option can be repeated)', + ) + dhcp_enable_group = parser.add_mutually_exclusive_group() + dhcp_enable_group.add_argument( + '--dhcp', + dest='enable_dhcp', + action='store_true', + default=True, + help='Enable DHCP (default)', + ) + dhcp_enable_group.add_argument( + '--no-dhcp', + dest='enable_dhcp', + action='store_false', + help='Disable DHCP', + ) + parser.add_argument( + '--dns-nameserver', + metavar='<dns-nameserver>', + action='append', + dest='dns_nameservers', + help='DNS name server for this subnet ' + '(This option can be repeated)', + ) + parser.add_argument( + '--gateway', + metavar='<gateway>', + default='auto', + help="Specify a gateway for the subnet. The three options are: " + " <ip-address>: Specific IP address to use as the gateway " + " 'auto': Gateway address should automatically be " + " chosen from within the subnet itself " + " 'none': This subnet will not use a gateway " + "e.g.: --gateway 192.168.9.1, --gateway auto, --gateway none" + "(default is 'auto')", + ) + parser.add_argument( + '--host-route', + metavar='destination=<subnet>,gateway=<ip-address>', + dest='host_routes', + action=parseractions.MultiKeyValueAction, + required_keys=['destination', 'gateway'], + help='Additional route for this subnet ' + 'e.g.: destination=10.10.0.0/16,gateway=192.168.71.254 ' + 'destination: destination subnet (in CIDR notation) ' + 'gateway: nexthop IP address ' + '(This option can be repeated)', + ) + parser.add_argument( + '--ip-version', + type=int, + default=4, + choices=[4, 6], + help='IP version (default is 4). Note that when subnet pool is ' + 'specified, IP version is determined from the subnet pool ' + 'and this option is ignored.', + ) + parser.add_argument( + '--ipv6-ra-mode', + choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], + help='IPv6 RA (Router Advertisement) mode, ' + 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]', + ) + parser.add_argument( + '--ipv6-address-mode', + choices=['dhcpv6-stateful', 'dhcpv6-stateless', 'slaac'], + help='IPv6 address mode, ' + 'valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]', + ) + parser.add_argument( + '--network', + required=True, + metavar='<network>', + help='Network this subnet belongs to (name or ID)', + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_subnet(**attrs) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (columns, data) + + class DeleteSubnet(command.Command): """Delete subnet""" @@ -46,7 +266,7 @@ class DeleteSubnet(command.Command): parser.add_argument( 'subnet', metavar="<subnet>", - help="Subnet to delete (name or ID)" + help="Subnet to delete (name or ID)", ) return parser @@ -97,7 +317,7 @@ class ShowSubnet(command.ShowOne): parser.add_argument( 'subnet', metavar="<subnet>", - help="Subnet to show (name or ID)" + help="Subnet to show (name or ID)", ) return parser diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 44f30207..d0d8d058 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -20,7 +20,7 @@ from openstackclient.common import utils def _get_columns(item): - columns = item.keys() + columns = list(item.keys()) if 'tenant_id' in columns: columns.remove('tenant_id') columns.append('project_id') diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 53e9be08..7750f2a3 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -355,7 +355,7 @@ class OpenStackShell(app.App): self.log.warning( "%s version %s is not in supported versions %s" % (api, version_opt, - ', '.join(mod.API_VERSIONS.keys()))) + ', '.join(list(mod.API_VERSIONS.keys())))) # Command groups deal only with major versions version = '.v' + version_opt.replace('.', '_').split('_')[0] diff --git a/openstackclient/tests/common/test_module.py b/openstackclient/tests/common/test_module.py index 2821da9e..7d08dae7 100644 --- a/openstackclient/tests/common/test_module.py +++ b/openstackclient/tests/common/test_module.py @@ -48,10 +48,11 @@ class TestCommandList(utils.TestCommand): super(TestCommandList, self).setUp() self.app.command_manager = mock.Mock() - self.app.command_manager.get_command_groups.return_value = ['test'] + self.app.command_manager.get_command_groups.return_value = [ + 'openstack.common' + ] self.app.command_manager.get_command_names.return_value = [ - 'one', - 'cmd two', + 'limits show\nextension list' ] # Get the command object to test @@ -67,12 +68,15 @@ class TestCommandList(utils.TestCommand): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) + # TODO(bapalm): Adjust this when cliff properly supports + # handling the detection rather than using the hard-code below. collist = ('Command Group', 'Commands') self.assertEqual(collist, columns) datalist = (( - 'test', - ['one', 'cmd two'], - ), ) + 'openstack.common', + 'limits show\nextension list' + ),) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index a4ee07bf..5c5ca3d3 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -91,11 +91,7 @@ class TestMultiKeyValueAction(utils.TestCase): {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] - # Need to sort the lists before comparing them - key = lambda x: x['req1'] - expect.sort(key=key) - actual.sort(key=key) - self.assertListEqual(expect, actual) + self.assertItemsEqual(expect, actual) def test_empty_required_optional(self): self.parser.add_argument( @@ -119,11 +115,7 @@ class TestMultiKeyValueAction(utils.TestCase): {'req1': 'aaa', 'req2': 'bbb'}, {'req1': '', 'req2': ''}, ] - # Need to sort the lists before comparing them - key = lambda x: x['req1'] - expect.sort(key=key) - actual.sort(key=key) - self.assertListEqual(expect, actual) + self.assertItemsEqual(expect, actual) def test_error_values_with_comma(self): self.assertRaises( diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 26d3a283..ccd8cc6b 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -79,10 +79,12 @@ QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) service_host = 'host_test' service_binary = 'compute_test' service_status = 'enabled' +service_disabled_reason = 'earthquake' SERVICE = { 'host': service_host, 'binary': service_binary, 'status': service_status, + 'disabled_reason': service_disabled_reason, } diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py index 0246fbc8..2feaf156 100644 --- a/openstackclient/tests/compute/v2/test_service.py +++ b/openstackclient/tests/compute/v2/test_service.py @@ -85,13 +85,37 @@ class TestServiceList(TestService): # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable # containing the data to be listed. - self.cmd.take_action(parsed_args) + columns, data = self.cmd.take_action(parsed_args) self.service_mock.list.assert_called_with( compute_fakes.service_host, compute_fakes.service_binary, ) + self.assertNotIn("Disabled Reason", columns) + self.assertNotIn(compute_fakes.service_disabled_reason, list(data)[0]) + + def test_service_list_with_long_option(self): + arglist = [ + '--host', compute_fakes.service_host, + '--service', compute_fakes.service_binary, + '--long' + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('long', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.assertIn("Disabled Reason", columns) + self.assertIn(compute_fakes.service_disabled_reason, list(data)[0]) + class TestServiceSet(TestService): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 2672d8e1..71543274 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -590,7 +590,7 @@ class FakeSubnet(object): 'dns_nameservers': [], 'allocation_pools': [], 'host_routes': [], - 'ip_version': '4', + 'ip_version': 4, 'gateway_ip': '10.10.10.1', 'ipv6_address_mode': 'None', 'ipv6_ra_mode': 'None', diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index a95635ff..b718d262 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -11,10 +11,13 @@ # under the License. # +import copy import mock from openstackclient.common import utils from openstackclient.network.v2 import subnet as subnet_v2 +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -28,6 +31,333 @@ class TestSubnet(network_fakes.TestNetworkV2): self.network = self.app.client_manager.network +class TestCreateSubnet(TestSubnet): + + # An IPv4 subnet to be created with mostly default values + _subnet = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + } + ) + + # Subnet pool to be used to create a subnet from a pool + _subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + + # An IPv4 subnet to be created using a specific subnet pool + _subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + 'subnetpool_id': _subnet_pool.id, + 'dns_nameservers': ['8.8.8.8', + '8.8.4.4'], + 'host_routes': [{'destination': '10.20.20.0/24', + 'nexthop': '10.20.20.1'}, + {'destination': '10.30.30.0/24', + 'nexthop': '10.30.30.1'}], + } + ) + + # An IPv6 subnet to be created with most options specified + _subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( + attrs={ + 'tenant_id': identity_fakes_v3.project_id, + 'cidr': 'fe80:0:0:a00a::/64', + 'enable_dhcp': True, + 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', + 'fe80:37ff:a00a:f00f::ffff'], + 'allocation_pools': [{'start': 'fe80::a00a:0:c0de:0:100', + 'end': 'fe80::a00a:0:c0de:0:f000'}, + {'start': 'fe80::a00a:0:c0de:1:100', + 'end': 'fe80::a00a:0:c0de:1:f000'}], + 'host_routes': [{'destination': 'fe80:27ff:a00a:f00f::/64', + 'nexthop': 'fe80:27ff:a00a:f00f::1'}, + {'destination': 'fe80:37ff:a00a:f00f::/64', + 'nexthop': 'fe80:37ff:a00a:f00f::1'}], + 'ip_version': 6, + 'gateway_ip': 'fe80::a00a:0:c0de:0:1', + 'ipv6_address_mode': 'slaac', + 'ipv6_ra_mode': 'slaac', + 'subnetpool_id': 'None', + } + ) + + # The network to be returned from find_network + _network = network_fakes.FakeNetwork.create_one_network( + attrs={ + 'id': _subnet.network_id, + } + ) + + columns = ( + 'allocation_pools', + 'cidr', + 'dns_nameservers', + 'enable_dhcp', + 'gateway_ip', + 'host_routes', + 'id', + 'ip_version', + 'ipv6_address_mode', + 'ipv6_ra_mode', + 'name', + 'network_id', + 'project_id', + 'subnetpool_id', + ) + + data = ( + subnet_v2._format_allocation_pools(_subnet.allocation_pools), + _subnet.cidr, + utils.format_list(_subnet.dns_nameservers), + _subnet.enable_dhcp, + _subnet.gateway_ip, + subnet_v2._format_host_routes(_subnet.host_routes), + _subnet.id, + _subnet.ip_version, + _subnet.ipv6_address_mode, + _subnet.ipv6_ra_mode, + _subnet.name, + _subnet.network_id, + _subnet.project_id, + _subnet.subnetpool_id, + ) + + data_subnet_pool = ( + subnet_v2._format_allocation_pools(_subnet_from_pool.allocation_pools), + _subnet_from_pool.cidr, + utils.format_list(_subnet_from_pool.dns_nameservers), + _subnet_from_pool.enable_dhcp, + _subnet_from_pool.gateway_ip, + subnet_v2._format_host_routes(_subnet_from_pool.host_routes), + _subnet_from_pool.id, + _subnet_from_pool.ip_version, + _subnet_from_pool.ipv6_address_mode, + _subnet_from_pool.ipv6_ra_mode, + _subnet_from_pool.name, + _subnet_from_pool.network_id, + _subnet_from_pool.project_id, + _subnet_from_pool.subnetpool_id, + ) + + data_ipv6 = ( + subnet_v2._format_allocation_pools(_subnet_ipv6.allocation_pools), + _subnet_ipv6.cidr, + utils.format_list(_subnet_ipv6.dns_nameservers), + _subnet_ipv6.enable_dhcp, + _subnet_ipv6.gateway_ip, + subnet_v2._format_host_routes(_subnet_ipv6.host_routes), + _subnet_ipv6.id, + _subnet_ipv6.ip_version, + _subnet_ipv6.ipv6_address_mode, + _subnet_ipv6.ipv6_ra_mode, + _subnet_ipv6.name, + _subnet_ipv6.network_id, + _subnet_ipv6.project_id, + _subnet_ipv6.subnetpool_id, + ) + + def setUp(self): + super(TestCreateSubnet, self).setUp() + + # Get the command object to test + self.cmd = subnet_v2.CreateSubnet(self.app, self.namespace) + + # Set identity client v3. And get a shortcut to Identity client. + identity_client = identity_fakes_v3.FakeIdentityv3Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.identity = identity_client + self.identity = self.app.client_manager.identity + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.identity.projects + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.PROJECT), + loaded=True, + ) + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.identity.domains + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes_v3.DOMAIN), + loaded=True, + ) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Testing that a call without the required argument will fail and + # throw a "ParserExecption" + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_create_default_options(self): + # Mock create_subnet and find_network sdk calls to return the + # values we want for this test + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self._network.id = self._subnet.network_id + self.network.find_network = mock.Mock(return_value=self._network) + + arglist = [ + self._subnet.name, + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + ] + verifylist = [ + ('name', self._subnet.name), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_with(**{ + 'cidr': self._subnet.cidr, + 'enable_dhcp': self._subnet.enable_dhcp, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_from_subnet_pool_options(self): + # Mock create_subnet, find_subnet_pool, and find_network sdk calls + # to return the values we want for this test + self.network.create_subnet = \ + mock.Mock(return_value=self._subnet_from_pool) + self._network.id = self._subnet_from_pool.network_id + self.network.find_network = mock.Mock(return_value=self._network) + self.network.find_subnet_pool = \ + mock.Mock(return_value=self._subnet_pool) + + arglist = [ + self._subnet_from_pool.name, + "--subnet-pool", self._subnet_from_pool.subnetpool_id, + "--prefix-length", '24', + "--network", self._subnet_from_pool.network_id, + "--ip-version", str(self._subnet_from_pool.ip_version), + "--gateway", self._subnet_from_pool.gateway_ip, + "--dhcp", + ] + + for dns_addr in self._subnet_from_pool.dns_nameservers: + arglist.append('--dns-nameserver') + arglist.append(dns_addr) + + for host_route in self._subnet_from_pool.host_routes: + arglist.append('--host-route') + value = 'gateway=' + host_route.get('nexthop', '') + \ + ',destination=' + host_route.get('destination', '') + arglist.append(value) + + verifylist = [ + ('name', self._subnet_from_pool.name), + ('prefix_length', '24'), + ('network', self._subnet_from_pool.network_id), + ('ip_version', self._subnet_from_pool.ip_version), + ('gateway', self._subnet_from_pool.gateway_ip), + ('dns_nameservers', self._subnet_from_pool.dns_nameservers), + ('enable_dhcp', self._subnet_from_pool.enable_dhcp), + ('host_routes', subnet_v2.convert_entries_to_gateway( + self._subnet_from_pool.host_routes)), + ('subnet_pool', self._subnet_from_pool.subnetpool_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_with(**{ + 'dns_nameservers': self._subnet_from_pool.dns_nameservers, + 'enable_dhcp': self._subnet_from_pool.enable_dhcp, + 'gateway_ip': self._subnet_from_pool.gateway_ip, + 'host_routes': self._subnet_from_pool.host_routes, + 'ip_version': self._subnet_from_pool.ip_version, + 'name': self._subnet_from_pool.name, + 'network_id': self._subnet_from_pool.network_id, + 'prefixlen': '24', + 'subnetpool_id': self._subnet_from_pool.subnetpool_id, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data_subnet_pool, data) + + def test_create_options_subnet_range_ipv6(self): + # Mock create_subnet and find_network sdk calls to return the + # values we want for this test + self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) + self._network.id = self._subnet_ipv6.network_id + self.network.find_network = mock.Mock(return_value=self._network) + + arglist = [ + self._subnet_ipv6.name, + "--subnet-range", self._subnet_ipv6.cidr, + "--network", self._subnet_ipv6.network_id, + "--ip-version", str(self._subnet_ipv6.ip_version), + "--ipv6-ra-mode", self._subnet_ipv6.ipv6_ra_mode, + "--ipv6-address-mode", self._subnet_ipv6.ipv6_address_mode, + "--gateway", self._subnet_ipv6.gateway_ip, + "--dhcp", + ] + + for dns_addr in self._subnet_ipv6.dns_nameservers: + arglist.append('--dns-nameserver') + arglist.append(dns_addr) + + for host_route in self._subnet_ipv6.host_routes: + arglist.append('--host-route') + value = 'gateway=' + host_route.get('nexthop', '') + \ + ',destination=' + host_route.get('destination', '') + arglist.append(value) + + for pool in self._subnet_ipv6.allocation_pools: + arglist.append('--allocation-pool') + value = 'start=' + pool.get('start', '') + \ + ',end=' + pool.get('end', '') + arglist.append(value) + + verifylist = [ + ('name', self._subnet_ipv6.name), + ('subnet_range', self._subnet_ipv6.cidr), + ('network', self._subnet_ipv6.network_id), + ('ip_version', self._subnet_ipv6.ip_version), + ('ipv6_ra_mode', self._subnet_ipv6.ipv6_ra_mode), + ('ipv6_address_mode', self._subnet_ipv6.ipv6_address_mode), + ('gateway', self._subnet_ipv6.gateway_ip), + ('dns_nameservers', self._subnet_ipv6.dns_nameservers), + ('enable_dhcp', self._subnet_ipv6.enable_dhcp), + ('host_routes', subnet_v2.convert_entries_to_gateway( + self._subnet_ipv6.host_routes)), + ('allocation_pools', self._subnet_ipv6.allocation_pools), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_with(**{ + 'cidr': self._subnet_ipv6.cidr, + 'dns_nameservers': self._subnet_ipv6.dns_nameservers, + 'enable_dhcp': self._subnet_ipv6.enable_dhcp, + 'gateway_ip': self._subnet_ipv6.gateway_ip, + 'host_routes': self._subnet_ipv6.host_routes, + 'ip_version': self._subnet_ipv6.ip_version, + 'ipv6_address_mode': self._subnet_ipv6.ipv6_address_mode, + 'ipv6_ra_mode': self._subnet_ipv6.ipv6_ra_mode, + 'name': self._subnet_ipv6.name, + 'network_id': self._subnet_ipv6.network_id, + 'allocation_pools': self._subnet_ipv6.allocation_pools, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data_ipv6, data) + + class TestDeleteSubnet(TestSubnet): # The subnet to delete. @@ -65,7 +395,7 @@ class TestListSubnet(TestSubnet): 'ID', 'Name', 'Network', - 'Subnet' + 'Subnet', ) columns_long = columns + ( 'Project', @@ -74,7 +404,7 @@ class TestListSubnet(TestSubnet): 'Allocation Pools', 'Host Routes', 'IP Version', - 'Gateway' + 'Gateway', ) data = [] @@ -99,7 +429,7 @@ class TestListSubnet(TestSubnet): subnet_v2._format_allocation_pools(subnet.allocation_pools), utils.format_list(subnet.host_routes), subnet.ip_version, - subnet.gateway_ip + subnet.gateway_ip, )) def setUp(self): |
