diff options
Diffstat (limited to 'openstackclient/network/v2')
| -rw-r--r-- | openstackclient/network/v2/floating_ip.py | 88 | ||||
| -rw-r--r-- | openstackclient/network/v2/network.py | 160 | ||||
| -rw-r--r-- | openstackclient/network/v2/port.py | 145 | ||||
| -rw-r--r-- | openstackclient/network/v2/router.py | 176 | ||||
| -rw-r--r-- | openstackclient/network/v2/security_group.py | 19 | ||||
| -rw-r--r-- | openstackclient/network/v2/security_group_rule.py | 317 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet.py | 283 | ||||
| -rw-r--r-- | openstackclient/network/v2/subnet_pool.py | 89 |
8 files changed, 1015 insertions, 262 deletions
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 16f2b574..21f86599 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -14,6 +14,7 @@ """IP Floating action implementations""" from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.network import common @@ -25,6 +26,89 @@ def _get_columns(item): 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""" @@ -32,7 +116,7 @@ class DeleteFloatingIP(common.NetworkAndComputeCommand): parser.add_argument( 'floating_ip', metavar="<floating-ip>", - help=("Floating IP to delete (IP address or ID)") + help=_("Floating IP to delete (IP address or ID)") ) return parser @@ -106,7 +190,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): parser.add_argument( 'floating_ip', metavar="<floating-ip>", - help=("Floating IP to display (IP address or ID)") + help=_("Floating IP to display (IP address or ID)") ) return parser diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 074b2754..4b77971a 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -16,6 +16,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -52,10 +53,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: @@ -72,18 +77,59 @@ def _get_attrs(client_manager, parsed_args): parsed_args.availability_zone_hints is not None: attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + # update_external_network_options + 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 + # Update Provider network options + 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 return attrs +def _add_provider_network_options(parser): + # Add provider network options + 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")) + + 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 - return attrs @@ -94,21 +140,19 @@ class CreateNetwork(common.NetworkAndComputeShowOne): parser.add_argument( 'name', metavar='<name>', - help='New network name', + help=_("New network name") ) 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', + help=_("Share the network between projects") ) share_group.add_argument( '--no-share', - dest='shared', - action='store_false', - help='Do not share the network between projects', + action='store_true', + help=_("Do not share the network between projects") ) return parser @@ -116,21 +160,19 @@ 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)', + help=_("Enable network (default)") ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', - help='Disable network', + action='store_true', + help=_("Disable network") ) parser.add_argument( '--project', metavar='<project>', - help="Owner's project (name or ID)" + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( @@ -138,17 +180,43 @@ class CreateNetwork(common.NetworkAndComputeShowOne): action='append', dest='availability_zone_hints', metavar='<availability-zone>', - help='Availability Zone in which to create this network ' - '(requires the Network Availability Zone extension, ' - 'this option can be repeated).', + help=_("Availability Zone in which to create this network " + "(Network Availability Zone extension required, " + "repeat option to set multiple availability zones)") ) + 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 " + "(external-net extension required)") + ) + 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. " + "(default)") + ) + _add_provider_network_options(parser) return parser def update_parser_compute(self, parser): parser.add_argument( '--subnet', metavar='<subnet>', - help="IPv4 subnet for fixed IPs (in CIDR notation)" + help=_("IPv4 subnet for fixed IPs (in CIDR notation)") ) return parser @@ -291,41 +359,61 @@ class SetNetwork(command.Command): parser.add_argument( 'network', metavar="<network>", - help=("Network to modify (name or ID)") + help=_("Network to modify (name or ID)") ) parser.add_argument( '--name', metavar='<name>', - help='Set network name', + help=_("Set network name") ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=None, - help='Enable network', + help=_("Enable network") ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', - help='Disable network', + 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', + help=_("Share the network between projects") ) share_group.add_argument( '--no-share', - dest='shared', - action='store_false', - help='Do not share the network between projects', + action='store_true', + help=_("Do not share the network between projects") + ) + 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 " + "(external-net extension required)") + ) + external_router_grp.add_argument( + '--internal', + action='store_true', + help=_("Set this network as an internal network") + ) + default_router_grp = parser.add_mutually_exclusive_group() + default_router_grp.add_argument( + '--default', + action='store_true', + help=_("Set the network 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") ) + _add_provider_network_options(parser) return parser def take_action(self, parsed_args): @@ -347,7 +435,7 @@ class ShowNetwork(common.NetworkAndComputeShowOne): parser.add_argument( 'network', metavar="<network>", - help=("Network to display (name or ID)") + help=_("Network to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 23350cf8..9b6161fd 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -30,6 +30,7 @@ LOG = logging.getLogger(__name__) def _format_admin_state(state): return 'UP' if state else 'DOWN' + _formatters = { 'admin_state_up': _format_admin_state, 'allowed_address_pairs': utils.format_list_of_dicts, @@ -86,8 +87,10 @@ def _get_attrs(client_manager, parsed_args): 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: @@ -95,10 +98,11 @@ def _get_attrs(client_manager, parsed_args): 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 'name' in parsed_args and parsed_args.name is not None: - attrs['name'] = str(parsed_args.name) if 'mac_address' in parsed_args and parsed_args.mac_address is not None: attrs['mac_address'] = parsed_args.mac_address if 'network' in parsed_args and parsed_args.network is not None: @@ -148,21 +152,13 @@ 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)') # 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', + help=_("Port device ID") ) device_group.add_argument( '--device-id', @@ -172,28 +168,23 @@ def _add_updatable_args(parser): parser.add_argument( '--device-owner', metavar='<device-owner>', - help='Device owner of this port') + help=_("Device owner of this port") + ) parser.add_argument( '--vnic-type', metavar='<vnic-type>', choices=['direct', 'direct-physical', 'macvtap', 'normal', 'baremetal'], - help="VNIC type for this port (direct | direct-physical |" - " macvtap | normal | baremetal). If unspecified during" - " port creation, default value will be 'normal'.") - 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)') + help=_("VNIC type for this port (direct | direct-physical | " + "macvtap | normal | baremetal, default: 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)', + help=_("Allocate port on host <host-id> (ID only)") ) host_group.add_argument( '--host-id', @@ -212,35 +203,54 @@ class CreatePort(command.ShowOne): '--network', metavar='<network>', required=True, - help='Network this port belongs to (name or ID)') + 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> " + "(repeat option to set multiple fixed IP addresses)") + ) + parser.add_argument( + '--binding-profile', + metavar='<binding-profile>', + action=parseractions.KeyValueAction, + help=_("Custom data to be passed as binding:profile: " + "<key>=<value> " + "(repeat option to set multiple binding:profile data)") + ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=True, - help='Enable port (default)', + help=_("Enable port (default)") ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', - help='Disable port', + action='store_true', + help=_("Disable port") ) parser.add_argument( '--mac-address', metavar='<mac-address>', - help='MAC address of this port') + help=_("MAC address of this port") + ) parser.add_argument( '--project', metavar='<project>', - help="Owner's project (name or ID)") + help=_("Owner's project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( 'name', metavar='<name>', - help='Name of this port') - identity_common.add_project_domain_option_to_parser(parser) + help=_("Name of this port") + ) # TODO(singhj): Add support for extended options: # qos,security groups,dhcp, address pairs return parser @@ -268,7 +278,7 @@ class DeletePort(command.Command): 'port', metavar="<port>", nargs="+", - help=("Port(s) to delete (name or ID)") + help=_("Port(s) to delete (name or ID)") ) return parser @@ -289,7 +299,7 @@ class ListPort(command.Lister): '--router', metavar='<router>', dest='router', - help='List only ports attached to this router (name or ID)', + help=_("List only ports attached to this router (name or ID)") ) return parser @@ -333,23 +343,54 @@ class SetPort(command.Command): admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state', action='store_true', default=None, - help='Enable port', + help=_("Enable port") ) admin_group.add_argument( '--disable', - dest='admin_state', - action='store_false', - help='Disable port', + action='store_true', + help=_("Disable port") + ) + parser.add_argument( + '--name', + metavar="<name>", + help=_("Set port name") + ) + 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> " + "(repeat option to set multiple fixed IP addresses)") + ) + fixed_ip.add_argument( + '--no-fixed-ip', + action='store_true', + help=_("Clear existing information of fixed IP addresses") + ) + 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> " + "(repeat option to set multiple binding:profile data)") + ) + binding_profile.add_argument( + '--no-binding-profile', + action='store_true', + help=_("Clear existing information of binding:profile") ) parser.add_argument( 'port', metavar="<port>", - help=("Port to modify (name or ID)") + help=_("Port to modify (name or ID)") ) - return parser def take_action(self, parsed_args): @@ -357,12 +398,24 @@ class SetPort(command.Command): _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.find_port(parsed_args.port, ignore_missing=False) + if 'binding:profile' in attrs: + attrs['binding:profile'].update(obj.binding_profile) + elif parsed_args.no_binding_profile: + attrs['binding:profile'] = {} + if 'fixed_ips' in attrs: + # When user unsets the fixed_ips, obj.fixed_ips = [{}]. + # Adding the obj.fixed_ips list to attrs['fixed_ips'] + # would therefore add an empty dictionary, while we need + # to append the attrs['fixed_ips'] iff there is some info + # in the obj.fixed_ips. Therefore I have opted for this `for` loop + attrs['fixed_ips'] += [ip for ip in obj.fixed_ips if ip] + elif parsed_args.no_fixed_ip: + attrs['fixed_ips'] = [] 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) @@ -374,7 +427,7 @@ class ShowPort(command.ShowOne): parser.add_argument( 'port', metavar="<port>", - help="Port to display (name or ID)" + help=_("Port to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 39431111..56630a23 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -19,6 +19,7 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common @@ -53,10 +54,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 @@ -82,6 +88,57 @@ def _get_attrs(client_manager, parsed_args): return attrs +class AddPortToRouter(command.Command): + """Add a port to a router""" + + def get_parser(self, prog_name): + parser = super(AddPortToRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='<router>', + help=_("Router to which port will be added (name or ID)") + ) + parser.add_argument( + 'port', + metavar='<port>', + help=_("Port to be added (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + port = client.find_port(parsed_args.port, ignore_missing=False) + client.router_add_interface(client.find_router( + parsed_args.router, ignore_missing=False), port_id=port.id) + + +class AddSubnetToRouter(command.Command): + """Add a subnet to a router""" + + def get_parser(self, prog_name): + parser = super(AddSubnetToRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='<router>', + help=_("Router to which subnet will be added (name or ID)") + ) + parser.add_argument( + 'subnet', + metavar='<subnet>', + help=_("Subnet to be added (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + subnet = client.find_subnet(parsed_args.subnet, + ignore_missing=False) + client.router_add_interface( + client.find_router(parsed_args.router, + ignore_missing=False), + subnet_id=subnet.id) + + class CreateRouter(command.ShowOne): """Create a new router""" @@ -90,45 +147,43 @@ class CreateRouter(command.ShowOne): parser.add_argument( 'name', metavar='<name>', - help="New router name", + help=_("New router name") ) 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)", + help=_("Enable router (default)") ) admin_group.add_argument( '--disable', - dest='admin_state_up', - action='store_false', - help="Disable router", + action='store_true', + help=_("Disable router") ) parser.add_argument( '--distributed', dest='distributed', action='store_true', default=False, - help="Create a distributed router", + help=_("Create a distributed router") ) parser.add_argument( '--project', metavar='<project>', - help="Owner's project (name or ID)", + help=_("Owner's project (name or ID)") ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--availability-zone-hint', metavar='<availability-zone>', action='append', dest='availability_zone_hints', - help='Availability Zone in which to create this router ' - '(requires the Router Availability Zone extension, ' - 'this option can be repeated).', + help=_("Availability Zone in which to create this router " + "(Router Availability Zone extension required, " + "repeat option to set multiple availability zones)") ) - identity_common.add_project_domain_option_to_parser(parser) return parser def take_action(self, parsed_args): @@ -152,7 +207,7 @@ class DeleteRouter(command.Command): 'router', metavar="<router>", nargs="+", - help=("Router(s) to delete (name or ID)") + help=_("Router(s) to delete (name or ID)") ) return parser @@ -172,7 +227,7 @@ class ListRouter(command.Lister): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -217,6 +272,57 @@ class ListRouter(command.Lister): ) for s in data)) +class RemovePortFromRouter(command.Command): + """Remove a port from a router""" + + def get_parser(self, prog_name): + parser = super(RemovePortFromRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='<router>', + help=_("Router from which port will be removed (name or ID)") + ) + parser.add_argument( + 'port', + metavar='<port>', + help=_("Port to be removed (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + port = client.find_port(parsed_args.port, ignore_missing=False) + client.router_remove_interface(client.find_router( + parsed_args.router, ignore_missing=False), port_id=port.id) + + +class RemoveSubnetFromRouter(command.Command): + """Remove a subnet from a router""" + + def get_parser(self, prog_name): + parser = super(RemoveSubnetFromRouter, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='<router>', + help=_("Router from which the subnet will be removed (name or ID)") + ) + parser.add_argument( + 'subnet', + metavar='<subnet>', + help=_("Subnet to be removed (name or ID)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + subnet = client.find_subnet(parsed_args.subnet, + ignore_missing=False) + client.router_remove_interface( + client.find_router(parsed_args.router, + ignore_missing=False), + subnet_id=subnet.id) + + class SetRouter(command.Command): """Set router properties""" @@ -225,40 +331,35 @@ class SetRouter(command.Command): parser.add_argument( 'router', metavar="<router>", - help=("Router to modify (name or ID)") + help=_("Router to modify (name or ID)") ) parser.add_argument( '--name', metavar='<name>', - help='Set router name', + help=_("Set router name") ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( '--enable', - dest='admin_state_up', action='store_true', default=None, - help='Enable router', + help=_("Enable router") ) admin_group.add_argument( '--disable', - dest='admin_state_up', - action='store_false', - help='Disable router', + 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)", + help=_("Set router to distributed mode (disabled router only)") ) distribute_group.add_argument( '--centralized', - dest='distributed', - action='store_false', - help="Set router to centralized mode (disabled router only)", + action='store_true', + help=_("Set router to centralized mode (disabled router only)") ) routes_group = parser.add_mutually_exclusive_group() routes_group.add_argument( @@ -268,16 +369,15 @@ class SetRouter(command.Command): dest='routes', default=None, required_keys=['destination', 'gateway'], - help="Routes associated with the router. " - "Repeat this option to set multiple routes. " - "destination: destination subnet (in CIDR notation). " - "gateway: nexthop IP address.", + help=_("Routes associated with the router " + "destination: destination subnet (in CIDR notation) " + "gateway: nexthop IP address " + "(repeat option to set multiple routes)") ) routes_group.add_argument( '--clear-routes', - dest='clear_routes', action='store_true', - help="Clear routes associated with the router", + help=_("Clear routes associated with the router") ) # TODO(tangchen): Support setting 'ha' property in 'router set' @@ -309,7 +409,7 @@ class ShowRouter(command.ShowOne): parser.add_argument( 'router', metavar="<router>", - help="Router to display (name or ID)" + help=_("Router to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 92498144..1ef2754e 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -17,6 +17,7 @@ import argparse import six from openstackclient.common import utils +from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -99,12 +100,12 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): parser.add_argument( "name", metavar="<name>", - help="New security group name", + help=_("New security group name") ) parser.add_argument( "--description", metavar="<description>", - help="Security group description", + help=_("Security group description") ) return parser @@ -112,7 +113,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): parser.add_argument( '--project', metavar='<project>', - help="Owner's project (name or ID)" + help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) return parser @@ -169,7 +170,7 @@ class DeleteSecurityGroup(common.NetworkAndComputeCommand): parser.add_argument( 'group', metavar='<group>', - help='Security group to delete (name or ID)', + help=_("Security group to delete (name or ID)") ) return parser @@ -204,7 +205,7 @@ class ListSecurityGroup(common.NetworkAndComputeLister): '--all-projects', action='store_true', default=False, - help='Display information from all projects (admin only)', + help=_("Display information from all projects (admin only)") ) return parser @@ -240,17 +241,17 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): parser.add_argument( 'group', metavar='<group>', - help='Security group to modify (name or ID)', + help=_("Security group to modify (name or ID)") ) parser.add_argument( '--name', metavar='<new-name>', - help='New security group name', + help=_("New security group name") ) parser.add_argument( "--description", metavar="<description>", - help="New security group description", + help=_("New security group description") ) return parser @@ -295,7 +296,7 @@ class ShowSecurityGroup(common.NetworkAndComputeShowOne): parser.add_argument( 'group', metavar='<group>', - help='Security group to display (name or ID)', + help=_("Security group to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 9309b326..5b22a0dd 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -13,10 +13,19 @@ """Security Group Rule action implementations""" +import argparse import six +try: + from novaclient.v2 import security_group_rules as compute_secgroup_rules +except ImportError: + from novaclient.v1_1 import security_group_rules as compute_secgroup_rules + from openstackclient.common import exceptions +from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import utils as network_utils @@ -26,6 +35,20 @@ def _format_security_group_rule_show(obj): return zip(*sorted(six.iteritems(data))) +def _format_network_port_range(rule): + port_range = '' + if (rule.protocol != 'icmp' and + (rule.port_range_min or rule.port_range_max)): + port_range_min = str(rule.port_range_min) + port_range_max = str(rule.port_range_max) + if rule.port_range_min is None: + port_range_min = port_range_max + if rule.port_range_max is None: + port_range_max = port_range_min + port_range = port_range_min + ':' + port_range_max + return port_range + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -34,6 +57,161 @@ def _get_columns(item): 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. When additional + # protocols are added, the default ethertype must be determined + # based on the protocol. + 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>", + help=_("Source IP address block (may use CIDR notation; " + "default for IPv4 rule: 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 update_parser_network(self, parser): + direction_group = parser.add_mutually_exclusive_group() + direction_group.add_argument( + '--ingress', + action='store_true', + help=_("Rule applies to incoming network traffic (default)") + ) + direction_group.add_argument( + '--egress', + action='store_true', + help=_("Rule applies to outgoing network traffic") + ) + parser.add_argument( + '--ethertype', + metavar='<ethertype>', + choices=['IPv4', 'IPv6'], + help=_("Ethertype of network traffic (IPv4, IPv6; default: IPv4)") + ) + 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_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 = {} + # NOTE(rtheis): A direction must be specified and ingress + # is the default. + if parsed_args.ingress or not parsed_args.egress: + attrs['direction'] = 'ingress' + if parsed_args.egress: + attrs['direction'] = 'egress' + if parsed_args.ethertype: + attrs['ethertype'] = parsed_args.ethertype + else: + # NOTE(rtheis): Default based on protocol is IPv4 for now. + # Once IPv6 protocols are added, this will need to be updated. + 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 + elif parsed_args.src_ip is not None: + attrs['remote_ip_prefix'] = parsed_args.src_ip + elif attrs['ethertype'] == 'IPv4': + attrs['remote_ip_prefix'] = '0.0.0.0/0' + attrs['security_group_id'] = security_group_id + 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 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 + src_ip = None + if parsed_args.src_group is not None: + parsed_args.src_group = utils.find_resource( + client.security_groups, + parsed_args.src_group, + ).id + if parsed_args.src_ip is not None: + src_ip = parsed_args.src_ip + else: + src_ip = '0.0.0.0/0' + obj = client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + src_ip, + parsed_args.src_group, + ) + return _format_security_group_rule_show(obj._info) + + class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): """Delete a security group rule""" @@ -41,7 +219,7 @@ class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): parser.add_argument( 'rule', metavar='<rule>', - help='Security group rule to delete (ID only)', + help=_("Security group rule to delete (ID only)") ) return parser @@ -53,6 +231,141 @@ class DeleteSecurityGroupRule(common.NetworkAndComputeCommand): client.security_group_rules.delete(parsed_args.rule) +class ListSecurityGroupRule(common.NetworkAndComputeLister): + """List security group rules""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='<group>', + nargs='?', + help=_("List all rules in this security group (name or ID)") + ) + return parser + + def update_parser_network(self, parser): + # Accept but hide the argument for consistency with compute. + # Network will always return all projects for an admin. + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=argparse.SUPPRESS + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def update_parser_compute(self, parser): + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help=_("Display information from all projects (admin only)") + ) + # Accept but hide the argument for consistency with network. + # There are no additional fields to display at this time. + parser.add_argument( + '--long', + action='store_false', + default=False, + help=argparse.SUPPRESS + ) + return parser + + def _get_column_headers(self, parsed_args): + column_headers = ( + 'ID', + 'IP Protocol', + 'IP Range', + 'Port Range', + ) + if parsed_args.long: + column_headers = column_headers + ('Direction', 'Ethertype',) + column_headers = column_headers + ('Remote Security Group',) + if parsed_args.group is None: + column_headers = column_headers + ('Security Group',) + return column_headers + + def take_action_network(self, client, parsed_args): + column_headers = self._get_column_headers(parsed_args) + columns = ( + 'id', + 'protocol', + 'remote_ip_prefix', + 'port_range_min', + ) + if parsed_args.long: + columns = columns + ('direction', 'ethertype',) + columns = columns + ('remote_group_id',) + + # Get the security group rules using the requested query. + query = {} + if parsed_args.group is not None: + # NOTE(rtheis): Unfortunately, the security group resource + # does not contain security group rules resources. So use + # the security group ID in a query to get the resources. + security_group_id = client.find_security_group( + parsed_args.group, + ignore_missing=False + ).id + query = {'security_group_id': security_group_id} + else: + columns = columns + ('security_group_id',) + rules = list(client.security_group_rules(**query)) + + # Reformat the rules to display a port range instead + # of just the port range minimum. This maintains + # output compatibility with compute. + for rule in rules: + rule.port_range_min = _format_network_port_range(rule) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) + + def take_action_compute(self, client, parsed_args): + column_headers = self._get_column_headers(parsed_args) + columns = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + "Remote Security Group", + ) + + rules_to_list = [] + if parsed_args.group is not None: + group = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + rules_to_list = group.rules + else: + columns = columns + ('parent_group_id',) + search = {'all_tenants': parsed_args.all_projects} + for group in client.security_groups.list(search_opts=search): + rules_to_list.extend(group.rules) + + # NOTE(rtheis): Turn the raw rules into resources. + rules = [] + for rule in rules_to_list: + rules.append(compute_secgroup_rules.SecurityGroupRule( + client.security_group_rules, + network_utils.transform_compute_security_group_rule(rule), + )) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) + + class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): """Display security group rule details""" @@ -60,7 +373,7 @@ class ShowSecurityGroupRule(common.NetworkAndComputeShowOne): parser.add_argument( 'rule', metavar="<rule>", - help="Security group rule to display (ID only)" + help=_("Security group rule to display (ID only)") ) return parser diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 794c787f..715e6620 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -17,8 +17,10 @@ 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.i18n import _ from openstackclient.identity import common as identity_common @@ -42,6 +44,39 @@ _formatters = { } +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 " + "(repeat option to add multiple IP addresses)") + ) + parser.add_argument( + '--dns-nameserver', + metavar='<dns-nameserver>', + action='append', + dest='dns_nameservers', + help=_("DNS server for this subnet " + "(repeat option to set multiple DNS servers)") + ) + 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 " + "(repeat option to add multiple routes)") + ) + + def _get_columns(item): columns = list(item.keys()) if 'tenant_id' in columns: @@ -61,7 +96,7 @@ def convert_entries_to_nexthop(entries): def convert_entries_to_gateway(entries): - # Change 'nexhop' entry to 'gateway' + # Change 'nexthop' entry to 'gateway' changed_entries = copy.deepcopy(entries) for entry in changed_entries: entry['gateway'] = entry['nexthop'] @@ -70,57 +105,66 @@ def convert_entries_to_gateway(entries): return changed_entries -def _get_attrs(client_manager, parsed_args): +def _get_attrs(client_manager, parsed_args, is_create=True): attrs = {} - if parsed_args.name is not None: + if 'name' in parsed_args and 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: + 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.enable_dhcp is not None: - attrs['enable_dhcp'] = parsed_args.enable_dhcp - if parsed_args.dns_nameservers is not None: + 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 parsed_args.host_routes is not None: + 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 @@ -131,122 +175,89 @@ class CreateSubnet(command.ShowOne): parser = super(CreateSubnet, self).get_parser(prog_name) parser.add_argument( 'name', - help='New subnet name', + help=_("New subnet name") ) parser.add_argument( '--project', metavar='<project>', - help="Owner's project (name or ID)", + 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)', + 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', + help=_("Use default subnet pool for --ip-version") ) parser.add_argument( '--prefix-length', metavar='<prefix-length>', - help='Prefix length for subnet allocation from subnetpool', + help=_("Prefix length for subnet allocation from subnet pool") ) 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)', + 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', - dest='enable_dhcp', action='store_true', default=True, - help='Enable DHCP (default)', + 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)', + 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( - '--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)', + 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.', + 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]', + 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]', + 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)', + help=_("Network this subnet belongs to (name or ID)") ) - + _get_common_parse_arguments(parser) return parser def take_action(self, parsed_args): @@ -266,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 @@ -285,7 +296,7 @@ class ListSubnet(command.Lister): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -309,15 +320,71 @@ 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) + if 'dns_nameservers' in attrs: + attrs['dns_nameservers'] += obj.dns_nameservers + if 'host_routes' in attrs: + attrs['host_routes'] += obj.host_routes + if 'allocation_pools' in attrs: + attrs['allocation_pools'] += obj.allocation_pools + client.update_subnet(obj, **attrs) + return + + class ShowSubnet(command.ShowOne): - """Show subnet details""" + """Display subnet details""" def get_parser(self, prog_name): parser = super(ShowSubnet, self).get_parser(prog_name) parser.add_argument( 'subnet', metavar="<subnet>", - help="Subnet to show (name or ID)", + help=_("Subnet to display (name or ID)") ) return parser diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index d0d8d058..482b5ecf 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -17,6 +17,8 @@ from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common def _get_columns(item): @@ -32,18 +34,36 @@ _formatters = { } -def _get_attrs(parsed_args): +def _get_attrs(client_manager, parsed_args): attrs = {} + network_client = client_manager.network + 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_prefix_length'] = parsed_args.default_prefix_length + attrs['default_prefixlen'] = parsed_args.default_prefix_length if parsed_args.min_prefix_length is not None: - attrs['min_prefix_length'] = parsed_args.min_prefix_length + attrs['min_prefixlen'] = parsed_args.min_prefix_length if parsed_args.max_prefix_length is not None: - attrs['max_prefix_length'] = parsed_args.max_prefix_length + attrs['max_prefixlen'] = parsed_args.max_prefix_length + + if parsed_args.address_scope is not None: + attrs['address_scope_id'] = network_client.find_address_scope( + parsed_args.address_scope, ignore_missing=False).id + if 'no_address_scope' in parsed_args and parsed_args.no_address_scope: + attrs['address_scope_id'] = None + + # "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 @@ -54,26 +74,26 @@ def _add_prefix_options(parser): metavar='<pool-prefix>', dest='prefixes', action='append', - help='Set subnet pool prefixes (in CIDR notation). ' - 'Repeat this option to set multiple prefixes.', + help=_("Set subnet pool prefixes (in CIDR notation) " + "(repeat 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', + 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', + 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', + help=_("Set subnet pool maximum prefix length") ) @@ -84,16 +104,31 @@ class CreateSubnetPool(command.ShowOne): parser = super(CreateSubnetPool, self).get_parser(prog_name) parser.add_argument( 'name', - metavar="<name>", - help='Name of the new subnet pool' + 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) + parser.add_argument( + '--address-scope', + metavar='<address-scope>', + help=_("Set address scope associated with the subnet pool " + "(name or ID), prefixes must be unique across address " + "scopes") + ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - attrs = _get_attrs(parsed_args) + 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) @@ -108,7 +143,7 @@ class DeleteSubnetPool(command.Command): parser.add_argument( 'subnet_pool', metavar='<subnet-pool>', - help='Subnet pool to delete (name or ID)' + help=_("Subnet pool to delete (name or ID)") ) return parser @@ -127,7 +162,7 @@ class ListSubnetPool(command.Lister): '--long', action='store_true', default=False, - help='List additional fields in output', + help=_("List additional fields in output") ) return parser @@ -164,7 +199,7 @@ class ListSubnetPool(command.Lister): return (headers, (utils.get_item_properties( s, columns, - formatters={}, + formatters=_formatters, ) for s in data)) @@ -176,15 +211,27 @@ class SetSubnetPool(command.Command): parser.add_argument( 'subnet_pool', metavar='<subnet-pool>', - help='Subnet pool to modify (name or ID)' + help=_("Subnet pool to modify (name or ID)") ) parser.add_argument( '--name', metavar='<name>', - help='Set subnet pool name', + help=_("Set subnet pool name") ) _add_prefix_options(parser) - + address_scope_group = parser.add_mutually_exclusive_group() + address_scope_group.add_argument( + '--address-scope', + metavar='<address-scope>', + help=_("Set address scope associated with the subnet pool " + "(name or ID), prefixes must be unique across address " + "scopes") + ) + address_scope_group.add_argument( + '--no-address-scope', + action='store_true', + help=_("Remove address scope associated with the subnet pool") + ) return parser def take_action(self, parsed_args): @@ -192,7 +239,7 @@ class SetSubnetPool(command.Command): obj = client.find_subnet_pool(parsed_args.subnet_pool, ignore_missing=False) - attrs = _get_attrs(parsed_args) + attrs = _get_attrs(self.app.client_manager, parsed_args) if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) @@ -212,7 +259,7 @@ class ShowSubnetPool(command.ShowOne): parser.add_argument( 'subnet_pool', metavar='<subnet-pool>', - help='Subnet pool to display (name or ID)' + help=_("Subnet pool to display (name or ID)") ) return parser |
