From b26b7f3440d4f756c0b7906b93751d7e83a733f7 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Tue, 22 Dec 2020 15:31:44 +0100 Subject: [PATCH] Allow to send extra attributes in Neutron related commands To deprecate and drop support for neutronclient CLI and use only OSC we need feature parity between OSC and neutronclient. Last missing piece here is possibility to send in POST/PUT requests unknown parameters to the Neutron server. This patch adds such possibility to the OSC. Change-Id: Iba09297c2be9fb9fa0be1b3dc65755277b79230e --- lower-constraints.txt | 2 +- openstackclient/network/common.py | 74 +++++++++- openstackclient/network/utils.py | 42 ++++++ openstackclient/network/v2/address_group.py | 11 +- openstackclient/network/v2/address_scope.py | 9 +- openstackclient/network/v2/floating_ip.py | 15 +- .../network/v2/floating_ip_port_forwarding.py | 12 +- openstackclient/network/v2/network.py | 17 ++- openstackclient/network/v2/network_flavor.py | 9 +- .../network/v2/network_flavor_profile.py | 10 +- openstackclient/network/v2/network_meter.py | 5 +- .../network/v2/network_meter_rule.py | 5 +- .../network/v2/network_qos_policy.py | 10 +- .../network/v2/network_qos_rule.py | 10 +- openstackclient/network/v2/network_rbac.py | 9 +- openstackclient/network/v2/network_segment.py | 10 +- .../network/v2/network_segment_range.py | 12 +- openstackclient/network/v2/port.py | 15 +- openstackclient/network/v2/router.py | 18 ++- openstackclient/network/v2/security_group.py | 10 +- .../network/v2/security_group_rule.py | 6 +- openstackclient/network/v2/subnet.py | 14 +- openstackclient/network/v2/subnet_pool.py | 10 +- .../tests/unit/network/test_common.py | 139 ++++++++++++++++++ .../tests/unit/network/test_utils.py | 59 ++++++++ requirements.txt | 2 +- 26 files changed, 486 insertions(+), 49 deletions(-) create mode 100644 openstackclient/tests/unit/network/test_utils.py diff --git a/lower-constraints.txt b/lower-constraints.txt index 09aabede1f..861749b738 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.53.0 +openstacksdk==0.56.0 os-client-config==2.1.0 os-service-types==1.7.0 osc-lib==2.3.0 diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 47ffbe77a6..b1902a6c9c 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -16,10 +16,12 @@ import contextlib import logging import openstack.exceptions +from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from openstackclient.i18n import _ +from openstackclient.network import utils LOG = logging.getLogger(__name__) @@ -75,7 +77,6 @@ class NetDetectionMixin(metaclass=abc.ABCMeta): """ # Have we set it up yet for this command? if not hasattr(self, '_net_type'): - # import pdb; pdb.set_trace() try: if self.app.client_manager.is_network_endpoint_enabled(): net_type = _NET_TYPE_NEUTRON @@ -255,3 +256,74 @@ class NetworkAndComputeShowOne(NetDetectionMixin, command.ShowOne, if exc.details: msg += ", " + str(exc.details) raise exceptions.CommandError(msg) + + +class NeutronCommandWithExtraArgs(command.Command): + """Create and Update commands with additional extra properties. + + Extra properties can be passed to the command and are then send to the + Neutron as given to the command. + """ + + # dict of allowed types + _allowed_types_dict = { + 'bool': utils.str2bool, + 'dict': utils.str2dict, + 'list': utils.str2list, + 'int': int, + 'str': str, + } + + def _get_property_converter(self, _property): + if 'type' not in _property: + converter = str + else: + converter = self._allowed_types_dict.get(_property['type']) + + if not converter: + raise exceptions.CommandError( + _("Type {property_type} of property {name} " + "is not supported").format( + property_type=_property['type'], + name=_property['name'])) + return converter + + def _parse_extra_properties(self, extra_properties): + result = {} + if extra_properties: + for _property in extra_properties: + converter = self._get_property_converter(_property) + result[_property['name']] = converter(_property['value']) + return result + + def get_parser(self, prog_name): + parser = super(NeutronCommandWithExtraArgs, self).get_parser(prog_name) + parser.add_argument( + '--extra-property', + metavar='type=,name=,' + 'value=', + dest='extra_properties', + action=parseractions.MultiKeyValueAction, + required_keys=['name', 'value'], + optional_keys=['type'], + help=_("Additional parameters can be passed using this property. " + "Default type of the extra property is string ('str'), but " + "other types can be used as well. Available types are: " + "'dict', 'list', 'str', 'bool', 'int'. " + "In case of 'list' type, 'value' can be " + "semicolon-separated list of values. " + "For 'dict' value is semicolon-separated list of the " + "key:value pairs.") + ) + return parser + + +class NeutronUnsetCommandWithExtraArgs(NeutronCommandWithExtraArgs): + + def _parse_extra_properties(self, extra_properties): + result = {} + if extra_properties: + for _property in extra_properties: + result[_property['name']] = None + + return result diff --git a/openstackclient/network/utils.py b/openstackclient/network/utils.py index 287f027163..4d4d18e470 100644 --- a/openstackclient/network/utils.py +++ b/openstackclient/network/utils.py @@ -11,6 +11,10 @@ # under the License. # +from osc_lib import exceptions + +from openstackclient.i18n import _ + # Transform compute security group rule for display. def transform_compute_security_group_rule(sg_rule): @@ -39,3 +43,41 @@ def transform_compute_security_group_rule(sg_rule): else: info['remote_security_group'] = '' return info + + +def str2bool(strbool): + if strbool is None: + return None + return strbool.lower() == 'true' + + +def str2list(strlist): + result = [] + if strlist: + result = strlist.split(';') + return result + + +def str2dict(strdict): + """Convert key1:value1;key2:value2;... string into dictionary. + + :param strdict: string in the form of key1:value1;key2:value2 + """ + result = {} + if not strdict: + return result + i = 0 + kvlist = [] + for kv in strdict.split(';'): + if ':' in kv: + kvlist.append(kv) + i += 1 + elif i == 0: + msg = _("missing value for key '%s'") + raise exceptions.CommandError(msg % kv) + else: + kvlist[i - 1] = "%s;%s" % (kvlist[i - 1], kv) + for kv in kvlist: + key, sep, value = kv.partition(':') + result[key] = value + return result diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index c5b2f12606..fc83470053 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -22,6 +22,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -57,7 +58,7 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateAddressGroup(command.ShowOne): +class CreateAddressGroup(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new Address Group") def get_parser(self, prog_name): @@ -93,6 +94,9 @@ class CreateAddressGroup(command.ShowOne): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_address_group(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -191,7 +195,7 @@ class ListAddressGroup(command.Lister): ) for s in data)) -class SetAddressGroup(command.Command): +class SetAddressGroup(common.NeutronCommandWithExtraArgs): _description = _("Set address group properties") def get_parser(self, prog_name): @@ -231,6 +235,9 @@ class SetAddressGroup(command.Command): attrs['name'] = parsed_args.name if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_address_group(obj, **attrs) if parsed_args.address: diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 71c1a9afb7..cd27678ee9 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -21,6 +21,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -57,7 +58,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateAddressScope(command.ShowOne): +class CreateAddressScope(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new Address Scope") def get_parser(self, prog_name): @@ -98,6 +99,8 @@ class CreateAddressScope(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_address_scope(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -226,7 +229,7 @@ class ListAddressScope(command.Lister): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetAddressScope(command.Command): +class SetAddressScope(common.NeutronCommandWithExtraArgs): _description = _("Set address scope properties") def get_parser(self, prog_name): @@ -267,6 +270,8 @@ class SetAddressScope(command.Command): attrs['shared'] = True if parsed_args.no_share: attrs['shared'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_address_scope(obj, **attrs) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index a2765cd1ef..25b2a1baf6 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -13,7 +13,6 @@ """IP Floating action implementations""" -from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -94,7 +93,8 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateFloatingIP(common.NetworkAndComputeShowOne): +class CreateFloatingIP(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create floating IP") def update_parser_common(self, parser): @@ -175,6 +175,8 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_ip(**attrs) @@ -390,7 +392,7 @@ class ListFloatingIP(common.NetworkAndComputeLister): ) for s in data)) -class SetFloatingIP(command.Command): +class SetFloatingIP(common.NeutronCommandWithExtraArgs): _description = _("Set floating IP Properties") def get_parser(self, prog_name): @@ -456,6 +458,9 @@ class SetFloatingIP(command.Command): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_ip(obj, **attrs) @@ -490,7 +495,7 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne): return (columns, data) -class UnsetFloatingIP(command.Command): +class UnsetFloatingIP(common.NeutronCommandWithExtraArgs): _description = _("Unset floating IP Properties") def get_parser(self, prog_name): @@ -526,6 +531,8 @@ class UnsetFloatingIP(command.Command): attrs['port_id'] = None if parsed_args.qos_policy: attrs['qos_policy_id'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: client.update_ip(obj, **attrs) diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index 06b3df8bcd..71b0b7da63 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -32,7 +33,8 @@ def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) -class CreateFloatingIPPortForwarding(command.ShowOne): +class CreateFloatingIPPortForwarding(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create floating IP port forwarding") def get_parser(self, prog_name): @@ -122,6 +124,9 @@ class CreateFloatingIPPortForwarding(command.ShowOne): if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_floating_ip_port_forwarding( floating_ip.id, **attrs @@ -258,7 +263,7 @@ class ListFloatingIPPortForwarding(command.Lister): ) for s in data)) -class SetFloatingIPPortForwarding(command.Command): +class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs): _description = _("Set floating IP Port Forwarding Properties") def get_parser(self, prog_name): @@ -352,6 +357,9 @@ class SetFloatingIPPortForwarding(command.Command): if parsed_args.description is not None: attrs['description'] = parsed_args.description + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + client.update_floating_ip_port_forwarding( floating_ip.id, parsed_args.port_forwarding_id, **attrs) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 7a12d523e2..b8eb9f014b 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -15,7 +15,6 @@ from cliff import columns as cliff_columns from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag @@ -189,7 +188,8 @@ def _add_additional_network_options(parser): # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetwork(common.NetworkAndComputeShowOne): +class CreateNetwork(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network") def update_parser_common(self, parser): @@ -334,6 +334,8 @@ class CreateNetwork(common.NetworkAndComputeShowOne): attrs['vlan_transparent'] = True if parsed_args.no_transparent_vlan: attrs['vlan_transparent'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_network(**attrs) @@ -623,7 +625,7 @@ class ListNetwork(common.NetworkAndComputeLister): # TODO(sindhu): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetwork(command.Command): +class SetNetwork(common.NeutronCommandWithExtraArgs): _description = _("Set network properties") def get_parser(self, prog_name): @@ -728,6 +730,8 @@ class SetNetwork(command.Command): obj = client.find_network(parsed_args.network, ignore_missing=False) attrs = _get_attrs_network(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): @@ -761,7 +765,7 @@ class ShowNetwork(common.NetworkAndComputeShowOne): return (display_columns, data) -class UnsetNetwork(command.Command): +class UnsetNetwork(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset network properties") def get_parser(self, prog_name): @@ -778,8 +782,9 @@ class UnsetNetwork(command.Command): client = self.app.client_manager.network obj = client.find_network(parsed_args.network, ignore_missing=False) - # NOTE: As of now, UnsetNetwork has no attributes which need - # to be updated by update_network(). + attrs = self._parse_extra_properties(parsed_args.extra_properties) + if attrs: + client.update_network(obj, **attrs) # tags is a subresource and it needs to be updated separately. _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index c9d368bfc1..9e758ae29c 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -21,6 +21,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -88,7 +89,7 @@ class AddNetworkFlavorToProfile(command.Command): # TODO(dasanind): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkFlavor(command.ShowOne): +class CreateNetworkFlavor(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create new network flavor") def get_parser(self, prog_name): @@ -134,6 +135,8 @@ class CreateNetworkFlavor(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_flavor(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -234,7 +237,7 @@ class RemoveNetworkFlavorFromProfile(command.Command): # TODO(dasanind): Use only the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkFlavor(command.Command): +class SetNetworkFlavor(common.NeutronCommandWithExtraArgs): _description = _("Set network flavor properties") def get_parser(self, prog_name): @@ -281,6 +284,8 @@ class SetNetworkFlavor(command.Command): attrs['enabled'] = True if parsed_args.disable: attrs['enabled'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_flavor(obj, **attrs) diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index 6cf0c4124d..0212e0d9ba 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -19,6 +19,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -60,7 +61,8 @@ def _get_attrs(client_manager, parsed_args): # TODO(ndahiwade): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkFlavorProfile(command.ShowOne): +class CreateNetworkFlavorProfile(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network flavor profile") def get_parser(self, prog_name): @@ -103,6 +105,8 @@ class CreateNetworkFlavorProfile(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if parsed_args.driver is None and parsed_args.metainfo is None: msg = _("Either --driver or --metainfo or both are required") @@ -180,7 +184,7 @@ class ListNetworkFlavorProfile(command.Lister): # TODO(ndahiwade): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkFlavorProfile(command.Command): +class SetNetworkFlavorProfile(common.NeutronCommandWithExtraArgs): _description = _("Set network flavor profile properties") def get_parser(self, prog_name): @@ -225,6 +229,8 @@ class SetNetworkFlavorProfile(command.Command): obj = client.find_service_profile(parsed_args.flavor_profile, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_service_profile(obj, **attrs) diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index df0e1da119..f8f188a806 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -21,6 +21,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -59,7 +60,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateMeter(command.ShowOne): +class CreateMeter(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create network meter") def get_parser(self, prog_name): @@ -100,6 +101,8 @@ class CreateMeter(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_metering_label(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 1cf0395f44..06362fa14c 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -21,6 +21,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils LOG = logging.getLogger(__name__) @@ -64,7 +65,7 @@ def _get_attrs(client_manager, parsed_args): return attrs -class CreateMeterRule(command.ShowOne): +class CreateMeterRule(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new meter rule") def get_parser(self, prog_name): @@ -130,6 +131,8 @@ class CreateMeterRule(command.ShowOne): ignore_missing=False) parsed_args.meter = _meter.id attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_metering_label_rule(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index fd5ff93771..7300a5c0ac 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -21,6 +21,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -67,7 +68,8 @@ def _get_attrs(client_manager, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkQosPolicy(command.ShowOne): +class CreateNetworkQosPolicy(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a QoS policy") def get_parser(self, prog_name): @@ -117,6 +119,8 @@ class CreateNetworkQosPolicy(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_qos_policy(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters={}) @@ -209,7 +213,7 @@ class ListNetworkQosPolicy(command.Lister): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkQosPolicy(command.Command): +class SetNetworkQosPolicy(common.NeutronCommandWithExtraArgs): _description = _("Set QoS policy properties") def get_parser(self, prog_name): @@ -259,6 +263,8 @@ class SetNetworkQosPolicy(command.Command): parsed_args.policy, ignore_missing=False) attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_qos_policy(obj, **attrs) diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 2e4b385d2e..f30a5aeb1f 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -20,6 +20,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -171,7 +172,8 @@ def _add_rule_arguments(parser): ) -class CreateNetworkQosRule(command.ShowOne): +class CreateNetworkQosRule(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new Network QoS rule") def get_parser(self, prog_name): @@ -198,6 +200,8 @@ class CreateNetworkQosRule(command.ShowOne): def take_action(self, parsed_args): network_client = self.app.client_manager.network attrs = _get_attrs(network_client, parsed_args, is_create=True) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) try: obj = _rule_action_call( network_client, ACTION_CREATE, parsed_args.type)( @@ -285,7 +289,7 @@ class ListNetworkQosRule(command.Lister): (_get_item_properties(s, columns) for s in data)) -class SetNetworkQosRule(command.Command): +class SetNetworkQosRule(common.NeutronCommandWithExtraArgs): _description = _("Set Network QoS rule properties") def get_parser(self, prog_name): @@ -312,6 +316,8 @@ class SetNetworkQosRule(command.Command): if not rule_type: raise Exception('Rule not found') attrs = _get_attrs(network_client, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) qos_id = attrs.pop('qos_policy_id') qos_rule = _rule_action_call(network_client, ACTION_FIND, rule_type)(attrs.pop('id'), qos_id) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 4984e89d56..692a43857d 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -21,6 +21,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -90,7 +91,7 @@ def _get_attrs(client_manager, parsed_args): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateNetworkRBAC(command.ShowOne): +class CreateNetworkRBAC(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create network RBAC policy") def get_parser(self, prog_name): @@ -150,6 +151,8 @@ class CreateNetworkRBAC(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_rbac_policy(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -253,7 +256,7 @@ class ListNetworkRBAC(command.Lister): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetNetworkRBAC(command.Command): +class SetNetworkRBAC(common.NeutronCommandWithExtraArgs): _description = _("Set network RBAC policy properties") def get_parser(self, prog_name): @@ -291,6 +294,8 @@ class SetNetworkRBAC(command.Command): parsed_args.target_project_domain, ).id attrs['target_tenant'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_rbac_policy(obj, **attrs) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index c1a672e2d5..14a8edabe5 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -20,6 +20,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -30,7 +31,8 @@ def _get_columns(item): return sdk_utils.get_osc_show_columns_for_sdk_resource(item, {}) -class CreateNetworkSegment(command.ShowOne): +class CreateNetworkSegment(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network segment") def get_parser(self, prog_name): @@ -88,6 +90,8 @@ class CreateNetworkSegment(command.ShowOne): attrs['physical_network'] = parsed_args.physical_network if parsed_args.segment is not None: attrs['segmentation_id'] = parsed_args.segment + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_segment(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -189,7 +193,7 @@ class ListNetworkSegment(command.Lister): ) for s in data)) -class SetNetworkSegment(command.Command): +class SetNetworkSegment(common.NeutronCommandWithExtraArgs): _description = _("Set network segment properties") def get_parser(self, prog_name): @@ -220,6 +224,8 @@ class SetNetworkSegment(command.Command): attrs['description'] = parsed_args.description if parsed_args.name is not None: attrs['name'] = parsed_args.name + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) client.update_segment(obj, **attrs) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index 6229995aab..ee414407ee 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -25,6 +25,7 @@ from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -87,7 +88,8 @@ def _update_additional_fields_from_props(columns, props): return props -class CreateNetworkSegmentRange(command.ShowOne): +class CreateNetworkSegmentRange(command.ShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create new network segment range") def get_parser(self, prog_name): @@ -209,6 +211,10 @@ class CreateNetworkSegmentRange(command.ShowOne): if parsed_args.physical_network: attrs['physical_network'] = parsed_args.physical_network + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = network_client.create_network_segment_range(**attrs) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns) @@ -365,7 +371,7 @@ class ListNetworkSegmentRange(command.Lister): return headers, display_props -class SetNetworkSegmentRange(command.Command): +class SetNetworkSegmentRange(common.NeutronCommandWithExtraArgs): _description = _("Set network segment range properties") def get_parser(self, prog_name): @@ -419,6 +425,8 @@ class SetNetworkSegmentRange(command.Command): attrs['minimum'] = parsed_args.minimum if parsed_args.maximum: attrs['maximum'] = parsed_args.maximum + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) network_client.update_network_segment_range(obj, **attrs) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 4feffc1df4..ecb2382a85 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -326,7 +326,7 @@ def _convert_extra_dhcp_options(parsed_args): return dhcp_options -class CreatePort(command.ShowOne): +class CreatePort(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new port") def get_parser(self, prog_name): @@ -501,6 +501,9 @@ class CreatePort(command.ShowOne): if parsed_args.tags: attrs['tags'] = list(set(parsed_args.tags)) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): obj = client.create_port(**attrs) @@ -697,7 +700,7 @@ class ListPort(command.Lister): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetPort(command.Command): +class SetPort(common.NeutronCommandWithExtraArgs): _description = _("Set port properties") def get_parser(self, prog_name): @@ -871,6 +874,9 @@ class SetPort(command.Command): if parsed_args.data_plane_status: attrs['data_plane_status'] = parsed_args.data_plane_status + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: with common.check_missing_extension_if_error( self.app.client_manager.network, attrs): @@ -902,7 +908,7 @@ class ShowPort(command.ShowOne): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class UnsetPort(command.Command): +class UnsetPort(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset port properties") def get_parser(self, prog_name): @@ -1023,6 +1029,9 @@ class UnsetPort(command.Command): if parsed_args.numa_policy: attrs['numa_affinity_policy'] = None + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_port(obj, **attrs) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index e3e8accd21..d15300a0b7 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -27,6 +27,7 @@ from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -256,7 +257,7 @@ class RemoveExtraRoutesFromRouter(command.ShowOne): # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateRouter(command.ShowOne): +class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a new router") def get_parser(self, prog_name): @@ -332,6 +333,9 @@ class CreateRouter(command.ShowOne): attrs['ha'] = True if parsed_args.no_ha: attrs['ha'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + obj = client.create_router(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -576,7 +580,7 @@ class RemoveSubnetFromRouter(command.Command): # TODO(yanxing'an): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetRouter(command.Command): +class SetRouter(common.NeutronCommandWithExtraArgs): _description = _("Set router properties") def get_parser(self, prog_name): @@ -767,6 +771,10 @@ class SetRouter(command.Command): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['external_gateway_info']['qos_policy_id'] = None + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -809,7 +817,7 @@ class ShowRouter(command.ShowOne): return (display_columns, data) -class UnsetRouter(command.Command): +class UnsetRouter(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset router properties") def get_parser(self, prog_name): @@ -875,6 +883,10 @@ class UnsetRouter(command.Command): if parsed_args.external_gateway: attrs['external_gateway_info'] = {} + + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_router(obj, **attrs) # tags is a subresource and it needs to be updated separately. diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 0732c23edc..49dc14e403 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -95,7 +95,8 @@ def _get_columns(item): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSecurityGroup(common.NetworkAndComputeShowOne): +class CreateSecurityGroup(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a new security group") def update_parser_common(self, parser): @@ -160,6 +161,8 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): parsed_args.project_domain, ).id attrs['tenant_id'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) # Create the security group and display the results. obj = client.create_security_group(**attrs) @@ -310,7 +313,8 @@ class ListSecurityGroup(common.NetworkAndComputeLister): ) for s in data)) -class SetSecurityGroup(common.NetworkAndComputeCommand): +class SetSecurityGroup(common.NetworkAndComputeCommand, + common.NeutronCommandWithExtraArgs): _description = _("Set security group properties") def update_parser_common(self, parser): @@ -362,6 +366,8 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): attrs['stateful'] = True if parsed_args.stateless: attrs['stateful'] = False + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) # NOTE(rtheis): Previous behavior did not raise a CommandError # if there were no updates. Maintain this behavior and issue # the update. diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 17241ed256..e273ded3d6 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -104,7 +104,8 @@ def _is_icmp_protocol(protocol): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): +class CreateSecurityGroupRule(common.NetworkAndComputeShowOne, + common.NeutronCommandWithExtraArgs): _description = _("Create a new security group rule") def update_parser_common(self, parser): @@ -355,6 +356,9 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne): ).id attrs['tenant_id'] = project_id + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + # Create and show the security group rule. obj = client.create_security_group_rule(**attrs) display_columns, columns = _get_columns(obj) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index eccdd4e460..09fd7c7c39 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -26,6 +26,7 @@ from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -250,7 +251,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSubnet(command.ShowOne): +class CreateSubnet(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create a subnet") def get_parser(self, prog_name): @@ -373,6 +374,8 @@ class CreateSubnet(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network attrs = _get_attrs(self.app.client_manager, parsed_args) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_subnet(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -545,7 +548,7 @@ class ListSubnet(command.Lister): # TODO(abhiraut): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetSubnet(command.Command): +class SetSubnet(common.NeutronCommandWithExtraArgs): _description = _("Set subnet properties") def get_parser(self, prog_name): @@ -630,6 +633,8 @@ class SetSubnet(command.Command): attrs['allocation_pools'] = [] if 'service_types' in attrs: attrs['service_types'] += obj.service_types + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) if attrs: client.update_subnet(obj, **attrs) # tags is a subresource and it needs to be updated separately. @@ -657,7 +662,7 @@ class ShowSubnet(command.ShowOne): return (display_columns, data) -class UnsetSubnet(command.Command): +class UnsetSubnet(common.NeutronUnsetCommandWithExtraArgs): _description = _("Unset subnet properties") def get_parser(self, prog_name): @@ -744,6 +749,9 @@ class UnsetSubnet(command.Command): _update_arguments(attrs['service_types'], parsed_args.service_types, 'service-type') + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_subnet(obj, **attrs) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 56cf61526c..bdf7aba805 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -24,6 +24,7 @@ from osc_lib.utils import tags as _tag from openstackclient.i18n import _ from openstackclient.identity import common as identity_common +from openstackclient.network import common from openstackclient.network import sdk_utils @@ -146,7 +147,7 @@ def _add_default_options(parser): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class CreateSubnetPool(command.ShowOne): +class CreateSubnetPool(command.ShowOne, common.NeutronCommandWithExtraArgs): _description = _("Create subnet pool") def get_parser(self, prog_name): @@ -203,6 +204,8 @@ class CreateSubnetPool(command.ShowOne): # NeutronServer expects prefixes to be a List if "prefixes" not in attrs: attrs['prefixes'] = [] + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) obj = client.create_subnet_pool(**attrs) # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) @@ -351,7 +354,7 @@ class ListSubnetPool(command.Lister): # TODO(rtheis): Use the SDK resource mapped attribute names once the # OSC minimum requirements include SDK 1.0. -class SetSubnetPool(command.Command): +class SetSubnetPool(common.NeutronCommandWithExtraArgs): _description = _("Set subnet pool properties") def get_parser(self, prog_name): @@ -408,6 +411,9 @@ class SetSubnetPool(command.Command): if 'prefixes' in attrs: attrs['prefixes'].extend(obj.prefixes) + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + if attrs: client.update_subnet_pool(obj, **attrs) # tags is a subresource and it needs to be updated separately. diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index cde321aa23..4dde1b2bea 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -102,6 +102,27 @@ class FakeNetworkAndComputeShowOne(common.NetworkAndComputeShowOne): return client.compute_action(parsed_args) +class FakeCreateNeutronCommandWithExtraArgs( + common.NeutronCommandWithExtraArgs): + + def get_parser(self, prog_name): + parser = super(FakeCreateNeutronCommandWithExtraArgs, + self).get_parser(prog_name) + parser.add_argument( + '--known-attribute', + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + if 'known_attribute' in parsed_args: + attrs['known_attribute'] = parsed_args.known_attribute + attrs.update( + self._parse_extra_properties(parsed_args.extra_properties)) + client.test_create_action(**attrs) + + class TestNetworkAndCompute(utils.TestCommand): def setUp(self): @@ -187,3 +208,121 @@ class TestNetworkAndComputeShowOne(TestNetworkAndCompute): m_action.side_effect = openstack.exceptions.HttpException("bar") self.assertRaisesRegex(exceptions.CommandError, "bar", self.cmd.take_action, mock.Mock()) + + +class TestNeutronCommandWithExtraArgs(utils.TestCommand): + + def setUp(self): + super(TestNeutronCommandWithExtraArgs, self).setUp() + + self.namespace = argparse.Namespace() + + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.test_create_action = mock.Mock() + + # Subclasses can override the command object to test. + self.cmd = FakeCreateNeutronCommandWithExtraArgs( + self.app, self.namespace) + + def test_create_extra_attributes_default_type(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'name=extra_name,value=extra_value' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'value': 'extra_value'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name='extra_value') + + def test_create_extra_attributes_string(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=str,name=extra_name,value=extra_value' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'str', + 'value': 'extra_value'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name='extra_value') + + def test_create_extra_attributes_bool(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=bool,name=extra_name,value=TrUe' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'bool', + 'value': 'TrUe'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=True) + + def test_create_extra_attributes_int(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=int,name=extra_name,value=8' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'int', + 'value': '8'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=8) + + def test_create_extra_attributes_list(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=list,name=extra_name,value=v_1;v_2' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'list', + 'value': 'v_1;v_2'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', extra_name=['v_1', 'v_2']) + + def test_create_extra_attributes_dict(self): + arglist = [ + '--known-attribute', 'known-value', + '--extra-property', 'type=dict,name=extra_name,value=n1:v1;n2:v2' + ] + verifylist = [ + ('known_attribute', 'known-value'), + ('extra_properties', [{'name': 'extra_name', + 'type': 'dict', + 'value': 'n1:v1;n2:v2'}]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.network.test_create_action.assert_called_with( + known_attribute='known-value', + extra_name={'n1': 'v1', 'n2': 'v2'}) diff --git a/openstackclient/tests/unit/network/test_utils.py b/openstackclient/tests/unit/network/test_utils.py new file mode 100644 index 0000000000..6252d7f766 --- /dev/null +++ b/openstackclient/tests/unit/network/test_utils.py @@ -0,0 +1,59 @@ +# 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. +# + +from osc_lib import exceptions + +from openstackclient.network import utils +from openstackclient.tests.unit import utils as tests_utils + + +class TestUtils(tests_utils.TestCase): + + def test_str2bool(self): + self.assertTrue(utils.str2bool("true")) + self.assertTrue(utils.str2bool("True")) + self.assertTrue(utils.str2bool("TRUE")) + self.assertTrue(utils.str2bool("TrUe")) + + self.assertFalse(utils.str2bool("false")) + self.assertFalse(utils.str2bool("False")) + self.assertFalse(utils.str2bool("FALSE")) + self.assertFalse(utils.str2bool("FaLsE")) + self.assertFalse(utils.str2bool("Something else")) + self.assertFalse(utils.str2bool("")) + + self.assertIsNone(utils.str2bool(None)) + + def test_str2list(self): + self.assertEqual( + ['a', 'b', 'c'], utils.str2list("a;b;c")) + self.assertEqual( + ['abc'], utils.str2list("abc")) + + self.assertEqual([], utils.str2list("")) + self.assertEqual([], utils.str2list(None)) + + def test_str2dict(self): + self.assertEqual( + {'a': 'aaa', 'b': '2'}, + utils.str2dict('a:aaa;b:2')) + self.assertEqual( + {'a': 'aaa;b;c', 'd': 'ddd'}, + utils.str2dict('a:aaa;b;c;d:ddd')) + + self.assertEqual({}, utils.str2dict("")) + self.assertEqual({}, utils.str2dict(None)) + + self.assertRaises( + exceptions.CommandError, + utils.str2dict, "aaa;b:2") diff --git a/requirements.txt b/requirements.txt index d3a17e9a48..0ac991da20 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.53.0 # Apache-2.0 +openstacksdk>=0.56.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0