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
This commit is contained in:
Slawek Kaplonski 2020-12-22 15:31:44 +01:00
parent 6bdf030953
commit b26b7f3440
26 changed files with 486 additions and 49 deletions

View File

@ -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

View File

@ -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=<property_type>,name=<property_name>,'
'value=<property_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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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={})

View File

@ -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={})

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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.

View File

@ -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'})

View File

@ -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")

View File

@ -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