Merge "Add "os subnet create" command using SDK"
This commit is contained in:
		| @@ -20,6 +20,114 @@ Delete a subnet | ||||
|  | ||||
|     Subnet to delete (name or ID) | ||||
|  | ||||
| subnet create | ||||
| -------------- | ||||
|  | ||||
| Create new subnet | ||||
|  | ||||
| .. program:: subnet create | ||||
| .. code:: bash | ||||
|  | ||||
|     os subnet create | ||||
|         [--project <project> [--project-domain <project-domain>]] | ||||
|         [--subnet-pool <subnet-pool> | --use-default-subnet-pool [--prefix-length <prefix-length>]] | ||||
|         [--subnet-range <subnet-range>] | ||||
|         [--allocation-pool start=<ip-address>,end=<ip-address>] | ||||
|         [--dhcp | --no-dhcp] | ||||
|         [--dns-nameserver <dns-nameserver>] | ||||
|         [--gateway <gateway>] | ||||
|         [--host-route destination=<subnet>,gateway=<ip-address>] | ||||
|         [--ip-version {4,6}] | ||||
|         [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] | ||||
|         [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] | ||||
|         --network <network> | ||||
|         <name> | ||||
|  | ||||
| .. option:: --project <project> | ||||
|  | ||||
|     Owner's project (name or ID) | ||||
|  | ||||
| .. option:: --project-domain <project-domain> | ||||
|  | ||||
|     Domain the project belongs to (name or ID). | ||||
|     This can be used in case collisions between project names exist. | ||||
|  | ||||
| .. option:: --subnet-pool <subnet-pool> | ||||
|  | ||||
|     Subnet pool from which this subnet will obtain a CIDR (name or ID) | ||||
|  | ||||
| .. option:: --use-default-subnet-pool | ||||
|  | ||||
|     Use default subnet pool for --ip-version | ||||
|  | ||||
| .. option:: --prefix-length <prefix-length> | ||||
|  | ||||
|     Prefix length for subnet allocation from subnet pool | ||||
|  | ||||
| .. option:: --subnet-range <subnet-range> | ||||
|  | ||||
|     Subnet range in CIDR notation | ||||
|         (required if --subnet-pool is not specified, optional otherwise) | ||||
|  | ||||
| .. option:: --allocation-pool start=<ip-address>,end=<ip-address> | ||||
|  | ||||
|     Allocation pool IP addresses for this subnet e.g.: | ||||
|         start=192.168.199.2,end=192.168.199.254 (This option can be repeated) | ||||
|  | ||||
| .. option:: --dhcp | ||||
|  | ||||
|      Enable DHCP (default) | ||||
|  | ||||
| .. option:: --no-dhcp | ||||
|  | ||||
|      Disable DHCP | ||||
|  | ||||
| .. option:: --dns-nameserver <dns-nameserver> | ||||
|  | ||||
|      DNS name server for this subnet (This option can be repeated) | ||||
|  | ||||
| .. option:: --gateway <gateway> | ||||
|  | ||||
|      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') | ||||
|  | ||||
| .. option:: --host-route destination=<subnet>,gateway=<ip-address> | ||||
|  | ||||
|      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) | ||||
|  | ||||
| .. option:: --ip-version {4,6} | ||||
|  | ||||
|      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. | ||||
|  | ||||
| .. option:: --ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} | ||||
|  | ||||
|      IPv6 RA (Router Advertisement) mode, | ||||
|          valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] | ||||
|  | ||||
| .. option:: --ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac} | ||||
|  | ||||
|      IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] | ||||
|  | ||||
| .. option:: --network <network> | ||||
|  | ||||
|      Network this subnet belongs to (name or ID) | ||||
|  | ||||
| .. _subnet_create-name: | ||||
| .. describe:: <name> | ||||
|  | ||||
|      Name of subnet to create | ||||
|  | ||||
| subnet list | ||||
| ----------- | ||||
|  | ||||
|   | ||||
| @@ -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,10 +28,17 @@ 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, | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -38,6 +50,214 @@ def _get_columns(item): | ||||
|     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 | ||||
|  | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
							
								
								
									
										5
									
								
								releasenotes/notes/bug-1542364-5d1e93cfd24f0b65.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								releasenotes/notes/bug-1542364-5d1e93cfd24f0b65.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| --- | ||||
| features: | ||||
|   - | | ||||
|     Add ``subnet create`` command. | ||||
|     [Bug `1542364 <https://bugs.launchpad.net/bugs/1542364>`_] | ||||
| @@ -351,6 +351,7 @@ openstack.network.v2 = | ||||
|     security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule | ||||
|     security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule | ||||
|  | ||||
|     subnet_create = openstackclient.network.v2.subnet:CreateSubnet | ||||
|     subnet_delete = openstackclient.network.v2.subnet:DeleteSubnet | ||||
|     subnet_list = openstackclient.network.v2.subnet:ListSubnet | ||||
|     subnet_show = openstackclient.network.v2.subnet:ShowSubnet | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jenkins
					Jenkins