summaryrefslogtreecommitdiff
path: root/openstackclient/network
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient/network')
-rw-r--r--openstackclient/network/utils.py41
-rw-r--r--openstackclient/network/v2/floating_ip.py85
-rw-r--r--openstackclient/network/v2/network.py91
-rw-r--r--openstackclient/network/v2/port.py198
-rw-r--r--openstackclient/network/v2/router.py29
-rw-r--r--openstackclient/network/v2/security_group.py179
-rw-r--r--openstackclient/network/v2/security_group_rule.py141
-rw-r--r--openstackclient/network/v2/subnet.py289
-rw-r--r--openstackclient/network/v2/subnet_pool.py136
9 files changed, 1081 insertions, 108 deletions
diff --git a/openstackclient/network/utils.py b/openstackclient/network/utils.py
new file mode 100644
index 00000000..287f0271
--- /dev/null
+++ b/openstackclient/network/utils.py
@@ -0,0 +1,41 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+
+# Transform compute security group rule for display.
+def transform_compute_security_group_rule(sg_rule):
+ info = {}
+ info.update(sg_rule)
+ from_port = info.pop('from_port')
+ to_port = info.pop('to_port')
+ if isinstance(from_port, int) and isinstance(to_port, int):
+ port_range = {'port_range': "%u:%u" % (from_port, to_port)}
+ elif from_port is None and to_port is None:
+ port_range = {'port_range': ""}
+ else:
+ port_range = {'port_range': "%s:%s" % (from_port, to_port)}
+ info.update(port_range)
+ if 'cidr' in info['ip_range']:
+ info['ip_range'] = info['ip_range']['cidr']
+ else:
+ info['ip_range'] = ''
+ if info['ip_protocol'] is None:
+ info['ip_protocol'] = ''
+ elif info['ip_protocol'].lower() == 'icmp':
+ info['port_range'] = ''
+ group = info.pop('group')
+ if 'name' in group:
+ info['remote_security_group'] = group['name']
+ else:
+ info['remote_security_group'] = ''
+ return info
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index e0a65a48..b21d6e96 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -18,13 +18,96 @@ 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')
return tuple(sorted(columns))
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ network_client = client_manager.network
+
+ if parsed_args.network is not None:
+ network = network_client.find_network(parsed_args.network,
+ ignore_missing=False)
+ attrs['floating_network_id'] = network.id
+
+ if parsed_args.subnet is not None:
+ subnet = network_client.find_subnet(parsed_args.subnet,
+ ignore_missing=False)
+ attrs['subnet_id'] = subnet.id
+
+ if parsed_args.port is not None:
+ port = network_client.find_port(parsed_args.port,
+ ignore_missing=False)
+ attrs['port_id'] = port.id
+
+ if parsed_args.floating_ip_address is not None:
+ attrs['floating_ip_address'] = parsed_args.floating_ip_address
+
+ if parsed_args.fixed_ip_address is not None:
+ attrs['fixed_ip_address'] = parsed_args.fixed_ip_address
+
+ return attrs
+
+
+class CreateFloatingIP(common.NetworkAndComputeShowOne):
+ """Create floating IP"""
+
+ def update_parser_common(self, parser):
+ # In Compute v2 network, floating IPs could be allocated from floating
+ # IP pools, which are actually external networks. So deprecate the
+ # parameter "pool", and use "network" instead.
+ parser.add_argument(
+ 'network',
+ metavar='<network>',
+ help='Network to allocate floating IP from (name or ID)',
+ )
+ return parser
+
+ def update_parser_network(self, parser):
+ parser.add_argument(
+ '--subnet',
+ metavar='<subnet>',
+ help="Subnet on which you want to create the floating IP "
+ "(name or ID)"
+ )
+ parser.add_argument(
+ '--port',
+ metavar='<port>',
+ help="Port to be associated with the floating IP "
+ "(name or ID)"
+ )
+ parser.add_argument(
+ '--floating-ip-address',
+ metavar='<floating-ip-address>',
+ dest='floating_ip_address',
+ help="Floating IP address"
+ )
+ parser.add_argument(
+ '--fixed-ip-address',
+ metavar='<fixed-ip-address>',
+ dest='fixed_ip_address',
+ help="Fixed IP address mapped to the floating IP"
+ )
+ return parser
+
+ def take_action_network(self, client, parsed_args):
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ obj = client.create_ip(**attrs)
+ columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return (columns, data)
+
+ def take_action_compute(self, client, parsed_args):
+ obj = client.floating_ips.create(parsed_args.network)
+ columns = _get_columns(obj._info)
+ data = utils.get_dict_properties(obj._info, columns)
+ return (columns, data)
+
+
class DeleteFloatingIP(common.NetworkAndComputeCommand):
"""Delete floating IP"""
diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py
index 612a1775..ebd5cb63 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')
@@ -52,10 +52,14 @@ def _get_attrs(client_manager, parsed_args):
attrs = {}
if parsed_args.name is not None:
attrs['name'] = str(parsed_args.name)
- if parsed_args.admin_state is not None:
- attrs['admin_state_up'] = parsed_args.admin_state
- if parsed_args.shared is not None:
- attrs['shared'] = parsed_args.shared
+ if parsed_args.enable:
+ attrs['admin_state_up'] = True
+ if parsed_args.disable:
+ attrs['admin_state_up'] = False
+ if parsed_args.share:
+ attrs['shared'] = True
+ if parsed_args.no_share:
+ attrs['shared'] = False
# "network set" command doesn't support setting project.
if 'project' in parsed_args and parsed_args.project is not None:
@@ -79,8 +83,10 @@ def _get_attrs_compute(client_manager, parsed_args):
attrs = {}
if parsed_args.name is not None:
attrs['label'] = str(parsed_args.name)
- if parsed_args.shared is not None:
- attrs['share_address'] = parsed_args.shared
+ if parsed_args.share:
+ attrs['share_address'] = True
+ if parsed_args.no_share:
+ attrs['share_address'] = False
if parsed_args.subnet is not None:
attrs['cidr'] = parsed_args.subnet
@@ -99,15 +105,13 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
share_group = parser.add_mutually_exclusive_group()
share_group.add_argument(
'--share',
- dest='shared',
action='store_true',
default=None,
help='Share the network between projects',
)
share_group.add_argument(
'--no-share',
- dest='shared',
- action='store_false',
+ action='store_true',
help='Do not share the network between projects',
)
return parser
@@ -116,15 +120,13 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
'--enable',
- dest='admin_state',
action='store_true',
default=True,
help='Enable network (default)',
)
admin_group.add_argument(
'--disable',
- dest='admin_state',
- action='store_false',
+ action='store_true',
help='Disable network',
)
parser.add_argument(
@@ -142,6 +144,47 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
'(requires the Network Availability Zone extension, '
'this option can be repeated).',
)
+ external_router_grp = parser.add_mutually_exclusive_group()
+ external_router_grp.add_argument(
+ '--external',
+ action='store_true',
+ help='Set this network as an external network. '
+ 'Requires the "external-net" extension to be enabled.')
+ external_router_grp.add_argument(
+ '--internal',
+ action='store_true',
+ help='Set this network as an internal network (default)')
+ default_router_grp = parser.add_mutually_exclusive_group()
+ default_router_grp.add_argument(
+ '--default',
+ action='store_true',
+ help='Specify if this network should be used as '
+ 'the default external network')
+ default_router_grp.add_argument(
+ '--no-default',
+ action='store_true',
+ help='Do not use the network as the default external network.'
+ 'By default, no network is set as an external network.')
+ parser.add_argument(
+ '--provider-network-type',
+ metavar='<provider-network-type>',
+ choices=['flat', 'gre', 'local',
+ 'vlan', 'vxlan'],
+ help='The physical mechanism by which the virtual network '
+ 'is implemented. The supported options are: '
+ 'flat, gre, local, vlan, vxlan')
+ parser.add_argument(
+ '--provider-physical-network',
+ metavar='<provider-physical-network>',
+ dest='physical_network',
+ help='Name of the physical network over which the virtual '
+ 'network is implemented')
+ parser.add_argument(
+ '--provider-segment',
+ metavar='<provider-segment>',
+ dest='segmentation_id',
+ help='VLAN ID for VLAN networks or Tunnel ID for GRE/VXLAN '
+ 'networks')
return parser
def update_parser_compute(self, parser):
@@ -154,6 +197,20 @@ class CreateNetwork(common.NetworkAndComputeShowOne):
def take_action_network(self, client, parsed_args):
attrs = _get_attrs(self.app.client_manager, parsed_args)
+ if parsed_args.internal:
+ attrs['router:external'] = False
+ if parsed_args.external:
+ attrs['router:external'] = True
+ if parsed_args.no_default:
+ attrs['is_default'] = False
+ if parsed_args.default:
+ attrs['is_default'] = True
+ if parsed_args.provider_network_type:
+ attrs['provider:network_type'] = parsed_args.provider_network_type
+ if parsed_args.physical_network:
+ attrs['provider:physical_network'] = parsed_args.physical_network
+ if parsed_args.segmentation_id:
+ attrs['provider:segmentation_id'] = parsed_args.segmentation_id
obj = client.create_network(**attrs)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
@@ -301,29 +358,25 @@ class SetNetwork(command.Command):
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
'--enable',
- dest='admin_state',
action='store_true',
default=None,
help='Enable network',
)
admin_group.add_argument(
'--disable',
- dest='admin_state',
- action='store_false',
+ action='store_true',
help='Disable network',
)
share_group = parser.add_mutually_exclusive_group()
share_group.add_argument(
'--share',
- dest='shared',
action='store_true',
default=None,
help='Share the network between projects',
)
share_group.add_argument(
'--no-share',
- dest='shared',
- action='store_false',
+ action='store_true',
help='Do not share the network between projects',
)
return parser
diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py
index 449dcfd4..a9e80428 100644
--- a/openstackclient/network/v2/port.py
+++ b/openstackclient/network/v2/port.py
@@ -13,12 +13,20 @@
"""Port action implementations"""
+import argparse
+import logging
+
from openstackclient.common import command
+from openstackclient.common import exceptions
from openstackclient.common import parseractions
from openstackclient.common import utils
+from openstackclient.i18n import _ # noqa
from openstackclient.identity import common as identity_common
+LOG = logging.getLogger(__name__)
+
+
def _format_admin_state(state):
return 'UP' if state else 'DOWN'
@@ -35,7 +43,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')
@@ -56,23 +64,42 @@ def _get_columns(item):
def _get_attrs(client_manager, parsed_args):
attrs = {}
- if parsed_args.name is not None:
- attrs['name'] = str(parsed_args.name)
+ # Handle deprecated options
+ # NOTE(dtroyer): --device-id and --host-id were deprecated in Mar 2016.
+ # Do not remove before 3.x release or Mar 2017.
+ if parsed_args.device_id:
+ attrs['device_id'] = parsed_args.device_id
+ LOG.warning(_(
+ 'The --device-id option is deprecated, '
+ 'please use --device instead.'
+ ))
+ if parsed_args.host_id:
+ attrs['binding:host_id'] = parsed_args.host_id
+ LOG.warning(_(
+ 'The --host-id option is deprecated, '
+ 'please use --host instead.'
+ ))
+
if parsed_args.fixed_ip is not None:
attrs['fixed_ips'] = parsed_args.fixed_ip
- if parsed_args.device_id is not None:
- attrs['device_id'] = parsed_args.device_id
+ if parsed_args.device:
+ attrs['device_id'] = parsed_args.device
if parsed_args.device_owner is not None:
attrs['device_owner'] = parsed_args.device_owner
- if parsed_args.admin_state is not None:
- attrs['admin_state_up'] = parsed_args.admin_state
+ if parsed_args.enable:
+ attrs['admin_state_up'] = True
+ if parsed_args.disable:
+ attrs['admin_state_up'] = False
if parsed_args.binding_profile is not None:
attrs['binding:profile'] = parsed_args.binding_profile
if parsed_args.vnic_type is not None:
attrs['binding:vnic_type'] = parsed_args.vnic_type
- if parsed_args.host_id is not None:
- attrs['binding:host_id'] = parsed_args.host_id
+ if parsed_args.host:
+ attrs['binding:host_id'] = parsed_args.host
+ # It is possible that name is not updated during 'port set'
+ if parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
# The remaining options do not support 'port set' command, so they require
# additional check
if 'mac_address' in parsed_args and parsed_args.mac_address is not None:
@@ -124,18 +151,19 @@ def _prepare_fixed_ips(client_manager, parsed_args):
def _add_updatable_args(parser):
- parser.add_argument(
- '--fixed-ip',
- metavar='subnet=<subnet>,ip-address=<ip-address>',
- action=parseractions.MultiKeyValueAction,
- optional_keys=['subnet', 'ip-address'],
- help='Desired IP and/or subnet (name or ID) for this port: '
- 'subnet=<subnet>,ip-address=<ip-address> '
- '(this option can be repeated)')
- parser.add_argument(
+ # NOTE(dtroyer): --device-id is deprecated in Mar 2016. Do not
+ # remove before 3.x release or Mar 2017.
+ device_group = parser.add_mutually_exclusive_group()
+ device_group.add_argument(
+ '--device',
+ metavar='<device-id>',
+ help='Port device ID',
+ )
+ device_group.add_argument(
'--device-id',
metavar='<device-id>',
- help='Device ID of this port')
+ help=argparse.SUPPRESS,
+ )
parser.add_argument(
'--device-owner',
metavar='<device-owner>',
@@ -145,18 +173,21 @@ def _add_updatable_args(parser):
metavar='<vnic-type>',
choices=['direct', 'direct-physical', 'macvtap',
'normal', 'baremetal'],
- help='VNIC type for this port (direct | direct-physical |'
- ' macvtap | normal(default) | baremetal)')
- parser.add_argument(
- '--binding-profile',
- metavar='<binding-profile>',
- action=parseractions.KeyValueAction,
- help='Custom data to be passed as binding:profile: <key>=<value> '
- '(this option can be repeated)')
- parser.add_argument(
+ help="VNIC type for this port (direct | direct-physical |"
+ " macvtap | normal | baremetal). If unspecified during"
+ " port creation, default value will be 'normal'.")
+ # NOTE(dtroyer): --host-id is deprecated in Mar 2016. Do not
+ # remove before 3.x release or Mar 2017.
+ host_group = parser.add_mutually_exclusive_group()
+ host_group.add_argument(
+ '--host',
+ metavar='<host-id>',
+ help='Allocate port on host <host-id> (ID only)',
+ )
+ host_group.add_argument(
'--host-id',
metavar='<host-id>',
- help='The ID of the host where the port is allocated'
+ help=argparse.SUPPRESS,
)
@@ -172,18 +203,30 @@ class CreatePort(command.ShowOne):
required=True,
help='Network this port belongs to (name or ID)')
_add_updatable_args(parser)
+ parser.add_argument(
+ '--fixed-ip',
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['subnet', 'ip-address'],
+ help='Desired IP and/or subnet (name or ID) for this port: '
+ 'subnet=<subnet>,ip-address=<ip-address> '
+ '(this option can be repeated)')
+ parser.add_argument(
+ '--binding-profile',
+ metavar='<binding-profile>',
+ action=parseractions.KeyValueAction,
+ help='Custom data to be passed as binding:profile: <key>=<value> '
+ '(this option can be repeated)')
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
'--enable',
- dest='admin_state',
action='store_true',
default=True,
help='Enable port (default)',
)
admin_group.add_argument(
'--disable',
- dest='admin_state',
- action='store_false',
+ action='store_true',
help='Disable port',
)
parser.add_argument(
@@ -241,6 +284,16 @@ class DeletePort(command.Command):
class ListPort(command.Lister):
"""List ports"""
+ def get_parser(self, prog_name):
+ parser = super(ListPort, self).get_parser(prog_name)
+ parser.add_argument(
+ '--router',
+ metavar='<router>',
+ dest='router',
+ help='List only ports attached to this router (name or ID)',
+ )
+ return parser
+
def take_action(self, parsed_args):
client = self.app.client_manager.network
@@ -257,7 +310,14 @@ class ListPort(command.Lister):
'Fixed IP Addresses',
)
- data = client.ports()
+ filters = {}
+ if parsed_args.router:
+ _router = client.find_router(parsed_args.router,
+ ignore_missing=False)
+ filters = {'device_id': _router.id}
+
+ data = client.ports(**filters)
+
return (column_headers,
(utils.get_item_properties(
s, columns,
@@ -265,6 +325,78 @@ class ListPort(command.Lister):
) for s in data))
+class SetPort(command.Command):
+ """Set port properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(SetPort, self).get_parser(prog_name)
+ _add_updatable_args(parser)
+ admin_group = parser.add_mutually_exclusive_group()
+ admin_group.add_argument(
+ '--enable',
+ action='store_true',
+ default=None,
+ help='Enable port',
+ )
+ admin_group.add_argument(
+ '--disable',
+ action='store_true',
+ help='Disable port',
+ )
+ parser.add_argument(
+ '--name',
+ metavar="<name>",
+ help=('Set port name'))
+ parser.add_argument(
+ 'port',
+ metavar="<port>",
+ help=("Port to modify (name or ID)")
+ )
+ fixed_ip = parser.add_mutually_exclusive_group()
+ fixed_ip.add_argument(
+ '--fixed-ip',
+ metavar='subnet=<subnet>,ip-address=<ip-address>',
+ action=parseractions.MultiKeyValueAction,
+ optional_keys=['subnet', 'ip-address'],
+ help='Desired IP and/or subnet (name or ID) for this port: '
+ 'subnet=<subnet>,ip-address=<ip-address> '
+ '(this option can be repeated)')
+ fixed_ip.add_argument(
+ '--no-fixed-ip',
+ action='store_true',
+ help='Clear existing information of fixed-ips')
+ binding_profile = parser.add_mutually_exclusive_group()
+ binding_profile.add_argument(
+ '--binding-profile',
+ metavar='<binding-profile>',
+ action=parseractions.KeyValueAction,
+ help='Custom data to be passed as binding:profile: <key>=<value> '
+ '(this option can be repeated)')
+ binding_profile.add_argument(
+ '--no-binding-profile',
+ action='store_true',
+ help='Clear existing information of binding:profile')
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ _prepare_fixed_ips(self.app.client_manager, parsed_args)
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+
+ if parsed_args.no_fixed_ip:
+ attrs['fixed_ips'] = []
+ if parsed_args.no_binding_profile:
+ attrs['binding:profile'] = {}
+
+ if attrs == {}:
+ msg = "Nothing specified to be set"
+ raise exceptions.CommandError(msg)
+
+ obj = client.find_port(parsed_args.port, ignore_missing=False)
+ client.update_port(obj, **attrs)
+
+
class ShowPort(command.ShowOne):
"""Display port details"""
diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py
index caf6d5ce..cd0f0e4c 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')
@@ -53,10 +53,15 @@ def _get_attrs(client_manager, parsed_args):
attrs = {}
if parsed_args.name is not None:
attrs['name'] = str(parsed_args.name)
- if parsed_args.admin_state_up is not None:
- attrs['admin_state_up'] = parsed_args.admin_state_up
- if parsed_args.distributed is not None:
- attrs['distributed'] = parsed_args.distributed
+ if parsed_args.enable:
+ attrs['admin_state_up'] = True
+ if parsed_args.disable:
+ attrs['admin_state_up'] = False
+ # centralized is available only for SetRouter and not for CreateRouter
+ if 'centralized' in parsed_args and parsed_args.centralized:
+ attrs['distributed'] = False
+ if parsed_args.distributed:
+ attrs['distributed'] = True
if ('availability_zone_hints' in parsed_args
and parsed_args.availability_zone_hints is not None):
attrs['availability_zone_hints'] = parsed_args.availability_zone_hints
@@ -119,15 +124,13 @@ class CreateRouter(command.ShowOne):
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
'--enable',
- dest='admin_state_up',
action='store_true',
default=True,
help="Enable router (default)",
)
admin_group.add_argument(
'--disable',
- dest='admin_state_up',
- action='store_false',
+ action='store_true',
help="Disable router",
)
parser.add_argument(
@@ -283,29 +286,24 @@ class SetRouter(command.Command):
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
'--enable',
- dest='admin_state_up',
action='store_true',
default=None,
help='Enable router',
)
admin_group.add_argument(
'--disable',
- dest='admin_state_up',
- action='store_false',
+ action='store_true',
help='Disable router',
)
distribute_group = parser.add_mutually_exclusive_group()
distribute_group.add_argument(
'--distributed',
- dest='distributed',
action='store_true',
- default=None,
help="Set router to distributed mode (disabled router only)",
)
distribute_group.add_argument(
'--centralized',
- dest='distributed',
- action='store_false',
+ action='store_true',
help="Set router to centralized mode (disabled router only)",
)
routes_group = parser.add_mutually_exclusive_group()
@@ -323,7 +321,6 @@ class SetRouter(command.Command):
)
routes_group.add_argument(
'--clear-routes',
- dest='clear_routes',
action='store_true',
help="Clear routes associated with the router",
)
diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py
index 62699ffd..92498144 100644
--- a/openstackclient/network/v2/security_group.py
+++ b/openstackclient/network/v2/security_group.py
@@ -14,9 +14,152 @@
"""Security Group action implementations"""
import argparse
+import six
from openstackclient.common import utils
+from openstackclient.identity import common as identity_common
from openstackclient.network import common
+from openstackclient.network import utils as network_utils
+
+
+def _format_network_security_group_rules(sg_rules):
+ # For readability and to align with formatting compute security group
+ # rules, trim keys with caller known (e.g. security group and tenant ID)
+ # or empty values.
+ for sg_rule in sg_rules:
+ empty_keys = [k for k, v in six.iteritems(sg_rule) if not v]
+ for key in empty_keys:
+ sg_rule.pop(key)
+ sg_rule.pop('security_group_id', None)
+ sg_rule.pop('tenant_id', None)
+ return utils.format_list_of_dicts(sg_rules)
+
+
+def _format_compute_security_group_rule(sg_rule):
+ info = network_utils.transform_compute_security_group_rule(sg_rule)
+ # Trim parent security group ID since caller has this information.
+ info.pop('parent_group_id', None)
+ # Trim keys with empty string values.
+ keys_to_trim = [
+ 'ip_protocol',
+ 'ip_range',
+ 'port_range',
+ 'remote_security_group',
+ ]
+ for key in keys_to_trim:
+ if key in info and not info[key]:
+ info.pop(key)
+ return utils.format_dict(info)
+
+
+def _format_compute_security_group_rules(sg_rules):
+ rules = []
+ for sg_rule in sg_rules:
+ rules.append(_format_compute_security_group_rule(sg_rule))
+ return utils.format_list(rules, separator='\n')
+
+
+_formatters_network = {
+ 'security_group_rules': _format_network_security_group_rules,
+}
+
+
+_formatters_compute = {
+ 'rules': _format_compute_security_group_rules,
+}
+
+
+def _get_columns(item):
+ # Build the display columns and a list of the property columns
+ # that need to be mapped (display column name, property name).
+ columns = list(item.keys())
+ property_column_mappings = []
+ if 'security_group_rules' in columns:
+ columns.append('rules')
+ columns.remove('security_group_rules')
+ property_column_mappings.append(('rules', 'security_group_rules'))
+ if 'tenant_id' in columns:
+ columns.append('project_id')
+ columns.remove('tenant_id')
+ property_column_mappings.append(('project_id', 'tenant_id'))
+ display_columns = sorted(columns)
+
+ # Build the property columns and apply any column mappings.
+ property_columns = sorted(columns)
+ for property_column_mapping in property_column_mappings:
+ property_index = property_columns.index(property_column_mapping[0])
+ property_columns[property_index] = property_column_mapping[1]
+ return tuple(display_columns), property_columns
+
+
+class CreateSecurityGroup(common.NetworkAndComputeShowOne):
+ """Create a new security group"""
+
+ def update_parser_common(self, parser):
+ parser.add_argument(
+ "name",
+ metavar="<name>",
+ help="New security group name",
+ )
+ parser.add_argument(
+ "--description",
+ metavar="<description>",
+ help="Security group description",
+ )
+ return parser
+
+ def update_parser_network(self, parser):
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help="Owner's project (name or ID)"
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ return parser
+
+ def _get_description(self, parsed_args):
+ if parsed_args.description is not None:
+ return parsed_args.description
+ else:
+ return parsed_args.name
+
+ def take_action_network(self, client, parsed_args):
+ # Build the create attributes.
+ attrs = {}
+ attrs['name'] = parsed_args.name
+ attrs['description'] = self._get_description(parsed_args)
+ if parsed_args.project is not None:
+ identity_client = self.app.client_manager.identity
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain,
+ ).id
+ attrs['tenant_id'] = project_id
+
+ # Create the security group and display the results.
+ obj = client.create_security_group(**attrs)
+ display_columns, property_columns = _get_columns(obj)
+ data = utils.get_item_properties(
+ obj,
+ property_columns,
+ formatters=_formatters_network
+ )
+ return (display_columns, data)
+
+ def take_action_compute(self, client, parsed_args):
+ description = self._get_description(parsed_args)
+ obj = client.security_groups.create(
+ parsed_args.name,
+ description,
+ )
+ display_columns, property_columns = _get_columns(obj._info)
+ data = utils.get_dict_properties(
+ obj._info,
+ property_columns,
+ formatters=_formatters_compute
+ )
+ return (display_columns, data)
class DeleteSecurityGroup(common.NetworkAndComputeCommand):
@@ -143,3 +286,39 @@ class SetSecurityGroup(common.NetworkAndComputeCommand):
data.name,
data.description,
)
+
+
+class ShowSecurityGroup(common.NetworkAndComputeShowOne):
+ """Display security group details"""
+
+ def update_parser_common(self, parser):
+ parser.add_argument(
+ 'group',
+ metavar='<group>',
+ help='Security group to display (name or ID)',
+ )
+ return parser
+
+ def take_action_network(self, client, parsed_args):
+ obj = client.find_security_group(parsed_args.group,
+ ignore_missing=False)
+ display_columns, property_columns = _get_columns(obj)
+ data = utils.get_item_properties(
+ obj,
+ property_columns,
+ formatters=_formatters_network
+ )
+ return (display_columns, data)
+
+ def take_action_compute(self, client, parsed_args):
+ obj = utils.find_resource(
+ client.security_groups,
+ parsed_args.group,
+ )
+ display_columns, property_columns = _get_columns(obj._info)
+ data = utils.get_dict_properties(
+ obj._info,
+ property_columns,
+ formatters=_formatters_compute
+ )
+ return (display_columns, data)
diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py
index a61e3233..f60995ab 100644
--- a/openstackclient/network/v2/security_group_rule.py
+++ b/openstackclient/network/v2/security_group_rule.py
@@ -16,51 +16,132 @@
import six
from openstackclient.common import exceptions
+from openstackclient.common import parseractions
from openstackclient.common import utils
from openstackclient.network import common
-
-
-def _xform_security_group_rule(sgroup):
- info = {}
- info.update(sgroup)
- from_port = info.pop('from_port')
- to_port = info.pop('to_port')
- if isinstance(from_port, int) and isinstance(to_port, int):
- port_range = {'port_range': "%u:%u" % (from_port, to_port)}
- elif from_port is None and to_port is None:
- port_range = {'port_range': ""}
- else:
- port_range = {'port_range': "%s:%s" % (from_port, to_port)}
- info.update(port_range)
- if 'cidr' in info['ip_range']:
- info['ip_range'] = info['ip_range']['cidr']
- else:
- info['ip_range'] = ''
- if info['ip_protocol'] is None:
- info['ip_protocol'] = ''
- elif info['ip_protocol'].lower() == 'icmp':
- info['port_range'] = ''
- group = info.pop('group')
- if 'name' in group:
- info['remote_security_group'] = group['name']
- else:
- info['remote_security_group'] = ''
- return info
+from openstackclient.network import utils as network_utils
def _format_security_group_rule_show(obj):
- data = _xform_security_group_rule(obj)
+ data = network_utils.transform_compute_security_group_rule(obj)
return zip(*sorted(six.iteritems(data)))
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_to_lowercase(string):
+ return string.lower()
+
+
+class CreateSecurityGroupRule(common.NetworkAndComputeShowOne):
+ """Create a new security group rule"""
+
+ def update_parser_common(self, parser):
+ parser.add_argument(
+ 'group',
+ metavar='<group>',
+ help='Create rule in this security group (name or ID)',
+ )
+ # TODO(rtheis): Add support for additional protocols for network.
+ # Until then, continue enforcing the compute choices.
+ parser.add_argument(
+ "--proto",
+ metavar="<proto>",
+ default="tcp",
+ choices=['icmp', 'tcp', 'udp'],
+ type=_convert_to_lowercase,
+ help="IP protocol (icmp, tcp, udp; default: tcp)",
+ )
+ source_group = parser.add_mutually_exclusive_group()
+ source_group.add_argument(
+ "--src-ip",
+ metavar="<ip-address>",
+ default="0.0.0.0/0",
+ help="Source IP address block (may use CIDR notation; default: "
+ "0.0.0.0/0)",
+ )
+ source_group.add_argument(
+ "--src-group",
+ metavar="<group>",
+ help="Source security group (name or ID)",
+ )
+ parser.add_argument(
+ "--dst-port",
+ metavar="<port-range>",
+ default=(0, 0),
+ action=parseractions.RangeAction,
+ help="Destination port, may be a single port or port range: "
+ "137:139 (only required for IP protocols tcp and udp)",
+ )
+ return parser
+
+ def take_action_network(self, client, parsed_args):
+ # Get the security group ID to hold the rule.
+ security_group_id = client.find_security_group(
+ parsed_args.group,
+ ignore_missing=False
+ ).id
+
+ # Build the create attributes.
+ attrs = {}
+ # TODO(rtheis): Add --direction option. Until then, continue
+ # with the default of 'ingress'.
+ attrs['direction'] = 'ingress'
+ # TODO(rtheis): Add --ethertype option. Until then, continue
+ # with the default of 'IPv4'
+ attrs['ethertype'] = 'IPv4'
+ # TODO(rtheis): Add port range support (type and code) for icmp
+ # protocol. Until then, continue ignoring the port range.
+ if parsed_args.proto != 'icmp':
+ attrs['port_range_min'] = parsed_args.dst_port[0]
+ attrs['port_range_max'] = parsed_args.dst_port[1]
+ attrs['protocol'] = parsed_args.proto
+ if parsed_args.src_group is not None:
+ attrs['remote_group_id'] = client.find_security_group(
+ parsed_args.src_group,
+ ignore_missing=False
+ ).id
+ else:
+ attrs['remote_ip_prefix'] = parsed_args.src_ip
+ attrs['security_group_id'] = security_group_id
+
+ # Create and show the security group rule.
+ obj = client.create_security_group_rule(**attrs)
+ columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return (columns, data)
+
+ def take_action_compute(self, client, parsed_args):
+ group = utils.find_resource(
+ client.security_groups,
+ parsed_args.group,
+ )
+ if parsed_args.proto == 'icmp':
+ from_port, to_port = -1, -1
+ else:
+ from_port, to_port = parsed_args.dst_port
+ if parsed_args.src_group is not None:
+ parsed_args.src_group = utils.find_resource(
+ client.security_groups,
+ parsed_args.src_group,
+ ).id
+ obj = client.security_group_rules.create(
+ group.id,
+ parsed_args.proto,
+ from_port,
+ to_port,
+ parsed_args.src_ip,
+ parsed_args.src_group,
+ )
+ return _format_security_group_rule_show(obj._info)
+
+
class DeleteSecurityGroupRule(common.NetworkAndComputeCommand):
"""Delete a security group rule"""
diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py
index b514a88f..10e5859a 100644
--- a/openstackclient/network/v2/subnet.py
+++ b/openstackclient/network/v2/subnet.py
@@ -12,9 +12,15 @@
#
"""Subnet action implementations"""
+import copy
+
+from json.encoder import JSONEncoder
from openstackclient.common import command
+from openstackclient.common import exceptions
+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 +29,246 @@ 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_common_parse_arguments(parser):
+ 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)',
+ )
+ 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(
+ '--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)',
+ )
+
+
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 'nexthop' 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, is_create=True):
+ attrs = {}
+ if 'name' in parsed_args and parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
+
+ if is_create:
+ 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.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 'gateway' in parsed_args and parsed_args.gateway is not None:
+ gateway = parsed_args.gateway.lower()
+
+ if not is_create and gateway == 'auto':
+ raise exceptions.CommandError("Auto option is not available"
+ " for Subnet Set. Valid options are"
+ " <ip-address> or none")
+ elif gateway != 'auto':
+ if gateway == 'none':
+ attrs['gateway_ip'] = None
+ else:
+ attrs['gateway_ip'] = gateway
+ if ('allocation_pools' in parsed_args and
+ parsed_args.allocation_pools is not None):
+ attrs['allocation_pools'] = parsed_args.allocation_pools
+ if parsed_args.dhcp:
+ attrs['enable_dhcp'] = True
+ elif parsed_args.no_dhcp:
+ attrs['enable_dhcp'] = False
+ if ('dns_nameservers' in parsed_args and
+ parsed_args.dns_nameservers is not None):
+ attrs['dns_nameservers'] = parsed_args.dns_nameservers
+ if 'host_routes' in parsed_args and 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)',
+ )
+ dhcp_enable_group = parser.add_mutually_exclusive_group()
+ dhcp_enable_group.add_argument(
+ '--dhcp',
+ action='store_true',
+ default=True,
+ help='Enable DHCP (default)',
+ )
+ dhcp_enable_group.add_argument(
+ '--no-dhcp',
+ action='store_true',
+ help='Disable DHCP',
+ )
+ 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(
+ '--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)',
+ )
+ _get_common_parse_arguments(parser)
+ 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 +277,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
@@ -89,6 +320,56 @@ class ListSubnet(command.Lister):
) for s in data))
+class SetSubnet(command.Command):
+ """Set subnet properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(SetSubnet, self).get_parser(prog_name)
+ parser.add_argument(
+ 'subnet',
+ metavar="<subnet>",
+ help=("Subnet to modify (name or ID)")
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help='Updated name of the subnet',
+ )
+ dhcp_enable_group = parser.add_mutually_exclusive_group()
+ dhcp_enable_group.add_argument(
+ '--dhcp',
+ action='store_true',
+ default=None,
+ help='Enable DHCP',
+ )
+ dhcp_enable_group.add_argument(
+ '--no-dhcp',
+ action='store_true',
+ help='Disable DHCP',
+ )
+ parser.add_argument(
+ '--gateway',
+ metavar='<gateway>',
+ help="Specify a gateway for the subnet. The options are: "
+ " <ip-address>: Specific IP address to use as the gateway "
+ " 'none': This subnet will not use a gateway "
+ "e.g.: --gateway 192.168.9.1, --gateway none"
+ )
+ _get_common_parse_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_subnet(parsed_args.subnet, ignore_missing=False)
+ attrs = _get_attrs(self.app.client_manager, parsed_args,
+ is_create=False)
+ if not attrs:
+ msg = "Nothing specified to be set"
+ raise exceptions.CommandError(msg)
+ client.update_subnet(obj, **attrs)
+ return
+
+
class ShowSubnet(command.ShowOne):
"""Show subnet details"""
@@ -97,7 +378,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 5bb45c12..6b6fc090 100644
--- a/openstackclient/network/v2/subnet_pool.py
+++ b/openstackclient/network/v2/subnet_pool.py
@@ -14,11 +14,14 @@
"""Subnet pool action implementations"""
from openstackclient.common import command
+from openstackclient.common import exceptions
+from openstackclient.common import parseractions
from openstackclient.common import utils
+from openstackclient.identity import common as identity_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')
@@ -30,6 +33,93 @@ _formatters = {
}
+def _get_attrs(client_manager, parsed_args):
+ attrs = {}
+ if parsed_args.name is not None:
+ attrs['name'] = str(parsed_args.name)
+ if parsed_args.prefixes is not None:
+ attrs['prefixes'] = parsed_args.prefixes
+ if parsed_args.default_prefix_length is not None:
+ attrs['default_prefixlen'] = parsed_args.default_prefix_length
+ if parsed_args.min_prefix_length is not None:
+ attrs['min_prefixlen'] = parsed_args.min_prefix_length
+ if parsed_args.max_prefix_length is not None:
+ attrs['max_prefixlen'] = parsed_args.max_prefix_length
+
+ # "subnet pool set" command doesn't support setting project.
+ 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
+
+ return attrs
+
+
+def _add_prefix_options(parser):
+ parser.add_argument(
+ '--pool-prefix',
+ metavar='<pool-prefix>',
+ dest='prefixes',
+ action='append',
+ help='Set subnet pool prefixes (in CIDR notation). '
+ 'Repeat this option to set multiple prefixes.',
+ )
+ parser.add_argument(
+ '--default-prefix-length',
+ metavar='<default-prefix-length>',
+ action=parseractions.NonNegativeAction,
+ help='Set subnet pool default prefix length',
+ )
+ parser.add_argument(
+ '--min-prefix-length',
+ metavar='<min-prefix-length>',
+ action=parseractions.NonNegativeAction,
+ help='Set subnet pool minimum prefix length',
+ )
+ parser.add_argument(
+ '--max-prefix-length',
+ metavar='<max-prefix-length>',
+ action=parseractions.NonNegativeAction,
+ help='Set subnet pool maximum prefix length',
+ )
+
+
+class CreateSubnetPool(command.ShowOne):
+ """Create subnet pool"""
+
+ def get_parser(self, prog_name):
+ parser = super(CreateSubnetPool, self).get_parser(prog_name)
+ parser.add_argument(
+ 'name',
+ metavar='<name>',
+ help='Name of the new subnet pool'
+ )
+ _add_prefix_options(parser)
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help="Owner's project (name or ID)",
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ # NeutronServer expects prefixes to be a List
+ if "prefixes" not in attrs:
+ attrs['prefixes'] = []
+ obj = client.create_subnet_pool(**attrs)
+ columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns, formatters=_formatters)
+ return (columns, data)
+
+
class DeleteSubnetPool(command.Command):
"""Delete subnet pool"""
@@ -37,8 +127,8 @@ class DeleteSubnetPool(command.Command):
parser = super(DeleteSubnetPool, self).get_parser(prog_name)
parser.add_argument(
'subnet_pool',
- metavar="<subnet-pool>",
- help=("Subnet pool to delete (name or ID)")
+ metavar='<subnet-pool>',
+ help='Subnet pool to delete (name or ID)'
)
return parser
@@ -98,6 +188,42 @@ class ListSubnetPool(command.Lister):
) for s in data))
+class SetSubnetPool(command.Command):
+ """Set subnet pool properties"""
+
+ def get_parser(self, prog_name):
+ parser = super(SetSubnetPool, self).get_parser(prog_name)
+ parser.add_argument(
+ 'subnet_pool',
+ metavar='<subnet-pool>',
+ help='Subnet pool to modify (name or ID)'
+ )
+ parser.add_argument(
+ '--name',
+ metavar='<name>',
+ help='Set subnet pool name',
+ )
+ _add_prefix_options(parser)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_subnet_pool(parsed_args.subnet_pool,
+ ignore_missing=False)
+
+ attrs = _get_attrs(self.app.client_manager, parsed_args)
+ if attrs == {}:
+ msg = "Nothing specified to be set"
+ raise exceptions.CommandError(msg)
+
+ # Existing prefixes must be a subset of the new prefixes.
+ if 'prefixes' in attrs:
+ attrs['prefixes'].extend(obj.prefixes)
+
+ client.update_subnet_pool(obj, **attrs)
+
+
class ShowSubnetPool(command.ShowOne):
"""Display subnet pool details"""
@@ -105,8 +231,8 @@ class ShowSubnetPool(command.ShowOne):
parser = super(ShowSubnetPool, self).get_parser(prog_name)
parser.add_argument(
'subnet_pool',
- metavar="<subnet-pool>",
- help=("Subnet pool to display (name or ID)")
+ metavar='<subnet-pool>',
+ help='Subnet pool to display (name or ID)'
)
return parser