Add "os subnet create" command using SDK

Implement the openstack client subnet create command using SDK
calls.

Co-Authored-By: Terry Howe <terrylhowe@gmail.com>
Partially implements: blueprint neutron-client
Closes-Bug: #1542364

Change-Id: Ia6120b8dccf2ee83dc89b3f496f7180d4dc5199a
This commit is contained in:
Brad Behle 2016-02-11 15:20:27 -06:00
parent 4bb48c088d
commit 71b8919054
6 changed files with 671 additions and 7 deletions

View File

@ -20,6 +20,114 @@ Delete a subnet
Subnet to delete (name or ID) 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 subnet list
----------- -----------

View File

@ -12,9 +12,14 @@
# #
"""Subnet action implementations""" """Subnet action implementations"""
import copy
from json.encoder import JSONEncoder
from openstackclient.common import command from openstackclient.common import command
from openstackclient.common import parseractions
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.identity import common as identity_common
def _format_allocation_pools(data): def _format_allocation_pools(data):
@ -23,10 +28,17 @@ def _format_allocation_pools(data):
return ','.join(pool_formatted) 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 = { _formatters = {
'allocation_pools': _format_allocation_pools, 'allocation_pools': _format_allocation_pools,
'dns_nameservers': utils.format_list, '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)) 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): class DeleteSubnet(command.Command):
"""Delete subnet""" """Delete subnet"""
@ -46,7 +266,7 @@ class DeleteSubnet(command.Command):
parser.add_argument( parser.add_argument(
'subnet', 'subnet',
metavar="<subnet>", metavar="<subnet>",
help="Subnet to delete (name or ID)" help="Subnet to delete (name or ID)",
) )
return parser return parser
@ -97,7 +317,7 @@ class ShowSubnet(command.ShowOne):
parser.add_argument( parser.add_argument(
'subnet', 'subnet',
metavar="<subnet>", metavar="<subnet>",
help="Subnet to show (name or ID)" help="Subnet to show (name or ID)",
) )
return parser return parser

View File

@ -573,7 +573,7 @@ class FakeSubnet(object):
'dns_nameservers': [], 'dns_nameservers': [],
'allocation_pools': [], 'allocation_pools': [],
'host_routes': [], 'host_routes': [],
'ip_version': '4', 'ip_version': 4,
'gateway_ip': '10.10.10.1', 'gateway_ip': '10.10.10.1',
'ipv6_address_mode': 'None', 'ipv6_address_mode': 'None',
'ipv6_ra_mode': 'None', 'ipv6_ra_mode': 'None',

View File

@ -11,10 +11,13 @@
# under the License. # under the License.
# #
import copy
import mock import mock
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.network.v2 import subnet as subnet_v2 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.network.v2 import fakes as network_fakes
from openstackclient.tests import utils as tests_utils from openstackclient.tests import utils as tests_utils
@ -28,6 +31,333 @@ class TestSubnet(network_fakes.TestNetworkV2):
self.network = self.app.client_manager.network 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): class TestDeleteSubnet(TestSubnet):
# The subnet to delete. # The subnet to delete.
@ -65,7 +395,7 @@ class TestListSubnet(TestSubnet):
'ID', 'ID',
'Name', 'Name',
'Network', 'Network',
'Subnet' 'Subnet',
) )
columns_long = columns + ( columns_long = columns + (
'Project', 'Project',
@ -74,7 +404,7 @@ class TestListSubnet(TestSubnet):
'Allocation Pools', 'Allocation Pools',
'Host Routes', 'Host Routes',
'IP Version', 'IP Version',
'Gateway' 'Gateway',
) )
data = [] data = []
@ -99,7 +429,7 @@ class TestListSubnet(TestSubnet):
subnet_v2._format_allocation_pools(subnet.allocation_pools), subnet_v2._format_allocation_pools(subnet.allocation_pools),
utils.format_list(subnet.host_routes), utils.format_list(subnet.host_routes),
subnet.ip_version, subnet.ip_version,
subnet.gateway_ip subnet.gateway_ip,
)) ))
def setUp(self): def setUp(self):

View File

@ -0,0 +1,5 @@
---
features:
- |
Add ``subnet create`` command.
[Bug `1542364 <https://bugs.launchpad.net/bugs/1542364>`_]

View File

@ -351,6 +351,7 @@ openstack.network.v2 =
security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule
security_group_rule_show = openstackclient.network.v2.security_group_rule:ShowSecurityGroupRule 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_delete = openstackclient.network.v2.subnet:DeleteSubnet
subnet_list = openstackclient.network.v2.subnet:ListSubnet subnet_list = openstackclient.network.v2.subnet:ListSubnet
subnet_show = openstackclient.network.v2.subnet:ShowSubnet subnet_show = openstackclient.network.v2.subnet:ShowSubnet