Skip to content

Commit b26b7f3

Browse files
committed
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
1 parent 6bdf030 commit b26b7f3

26 files changed

Lines changed: 486 additions & 49 deletions

lower-constraints.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ msgpack-python==0.4.0
3838
munch==2.1.0
3939
netaddr==0.7.18
4040
netifaces==0.10.4
41-
openstacksdk==0.53.0
41+
openstacksdk==0.56.0
4242
os-client-config==2.1.0
4343
os-service-types==1.7.0
4444
osc-lib==2.3.0

openstackclient/network/common.py

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
import logging
1717

1818
import openstack.exceptions
19+
from osc_lib.cli import parseractions
1920
from osc_lib.command import command
2021
from osc_lib import exceptions
2122

2223
from openstackclient.i18n import _
24+
from openstackclient.network import utils
2325

2426

2527
LOG = logging.getLogger(__name__)
@@ -75,7 +77,6 @@ def _network_type(self):
7577
"""
7678
# Have we set it up yet for this command?
7779
if not hasattr(self, '_net_type'):
78-
# import pdb; pdb.set_trace()
7980
try:
8081
if self.app.client_manager.is_network_endpoint_enabled():
8182
net_type = _NET_TYPE_NEUTRON
@@ -255,3 +256,74 @@ def take_action(self, parsed_args):
255256
if exc.details:
256257
msg += ", " + str(exc.details)
257258
raise exceptions.CommandError(msg)
259+
260+
261+
class NeutronCommandWithExtraArgs(command.Command):
262+
"""Create and Update commands with additional extra properties.
263+
264+
Extra properties can be passed to the command and are then send to the
265+
Neutron as given to the command.
266+
"""
267+
268+
# dict of allowed types
269+
_allowed_types_dict = {
270+
'bool': utils.str2bool,
271+
'dict': utils.str2dict,
272+
'list': utils.str2list,
273+
'int': int,
274+
'str': str,
275+
}
276+
277+
def _get_property_converter(self, _property):
278+
if 'type' not in _property:
279+
converter = str
280+
else:
281+
converter = self._allowed_types_dict.get(_property['type'])
282+
283+
if not converter:
284+
raise exceptions.CommandError(
285+
_("Type {property_type} of property {name} "
286+
"is not supported").format(
287+
property_type=_property['type'],
288+
name=_property['name']))
289+
return converter
290+
291+
def _parse_extra_properties(self, extra_properties):
292+
result = {}
293+
if extra_properties:
294+
for _property in extra_properties:
295+
converter = self._get_property_converter(_property)
296+
result[_property['name']] = converter(_property['value'])
297+
return result
298+
299+
def get_parser(self, prog_name):
300+
parser = super(NeutronCommandWithExtraArgs, self).get_parser(prog_name)
301+
parser.add_argument(
302+
'--extra-property',
303+
metavar='type=<property_type>,name=<property_name>,'
304+
'value=<property_value>',
305+
dest='extra_properties',
306+
action=parseractions.MultiKeyValueAction,
307+
required_keys=['name', 'value'],
308+
optional_keys=['type'],
309+
help=_("Additional parameters can be passed using this property. "
310+
"Default type of the extra property is string ('str'), but "
311+
"other types can be used as well. Available types are: "
312+
"'dict', 'list', 'str', 'bool', 'int'. "
313+
"In case of 'list' type, 'value' can be "
314+
"semicolon-separated list of values. "
315+
"For 'dict' value is semicolon-separated list of the "
316+
"key:value pairs.")
317+
)
318+
return parser
319+
320+
321+
class NeutronUnsetCommandWithExtraArgs(NeutronCommandWithExtraArgs):
322+
323+
def _parse_extra_properties(self, extra_properties):
324+
result = {}
325+
if extra_properties:
326+
for _property in extra_properties:
327+
result[_property['name']] = None
328+
329+
return result

openstackclient/network/utils.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
# under the License.
1212
#
1313

14+
from osc_lib import exceptions
15+
16+
from openstackclient.i18n import _
17+
1418

1519
# Transform compute security group rule for display.
1620
def transform_compute_security_group_rule(sg_rule):
@@ -39,3 +43,41 @@ def transform_compute_security_group_rule(sg_rule):
3943
else:
4044
info['remote_security_group'] = ''
4145
return info
46+
47+
48+
def str2bool(strbool):
49+
if strbool is None:
50+
return None
51+
return strbool.lower() == 'true'
52+
53+
54+
def str2list(strlist):
55+
result = []
56+
if strlist:
57+
result = strlist.split(';')
58+
return result
59+
60+
61+
def str2dict(strdict):
62+
"""Convert key1:value1;key2:value2;... string into dictionary.
63+
64+
:param strdict: string in the form of key1:value1;key2:value2
65+
"""
66+
result = {}
67+
if not strdict:
68+
return result
69+
i = 0
70+
kvlist = []
71+
for kv in strdict.split(';'):
72+
if ':' in kv:
73+
kvlist.append(kv)
74+
i += 1
75+
elif i == 0:
76+
msg = _("missing value for key '%s'")
77+
raise exceptions.CommandError(msg % kv)
78+
else:
79+
kvlist[i - 1] = "%s;%s" % (kvlist[i - 1], kv)
80+
for kv in kvlist:
81+
key, sep, value = kv.partition(':')
82+
result[key] = value
83+
return result

openstackclient/network/v2/address_group.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from openstackclient.i18n import _
2424
from openstackclient.identity import common as identity_common
25+
from openstackclient.network import common
2526
from openstackclient.network import sdk_utils
2627

2728

@@ -57,7 +58,7 @@ def _get_attrs(client_manager, parsed_args):
5758
return attrs
5859

5960

60-
class CreateAddressGroup(command.ShowOne):
61+
class CreateAddressGroup(command.ShowOne, common.NeutronCommandWithExtraArgs):
6162
_description = _("Create a new Address Group")
6263

6364
def get_parser(self, prog_name):
@@ -93,6 +94,9 @@ def take_action(self, parsed_args):
9394
client = self.app.client_manager.network
9495
attrs = _get_attrs(self.app.client_manager, parsed_args)
9596

97+
attrs.update(
98+
self._parse_extra_properties(parsed_args.extra_properties))
99+
96100
obj = client.create_address_group(**attrs)
97101
display_columns, columns = _get_columns(obj)
98102
data = utils.get_item_properties(obj, columns, formatters={})
@@ -191,7 +195,7 @@ def take_action(self, parsed_args):
191195
) for s in data))
192196

193197

194-
class SetAddressGroup(command.Command):
198+
class SetAddressGroup(common.NeutronCommandWithExtraArgs):
195199
_description = _("Set address group properties")
196200

197201
def get_parser(self, prog_name):
@@ -231,6 +235,9 @@ def take_action(self, parsed_args):
231235
attrs['name'] = parsed_args.name
232236
if parsed_args.description is not None:
233237
attrs['description'] = parsed_args.description
238+
attrs.update(
239+
self._parse_extra_properties(parsed_args.extra_properties))
240+
234241
if attrs:
235242
client.update_address_group(obj, **attrs)
236243
if parsed_args.address:

openstackclient/network/v2/address_scope.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from openstackclient.i18n import _
2323
from openstackclient.identity import common as identity_common
24+
from openstackclient.network import common
2425
from openstackclient.network import sdk_utils
2526

2627

@@ -57,7 +58,7 @@ def _get_attrs(client_manager, parsed_args):
5758

5859
# TODO(rtheis): Use the SDK resource mapped attribute names once the
5960
# OSC minimum requirements include SDK 1.0.
60-
class CreateAddressScope(command.ShowOne):
61+
class CreateAddressScope(command.ShowOne, common.NeutronCommandWithExtraArgs):
6162
_description = _("Create a new Address Scope")
6263

6364
def get_parser(self, prog_name):
@@ -98,6 +99,8 @@ def get_parser(self, prog_name):
9899
def take_action(self, parsed_args):
99100
client = self.app.client_manager.network
100101
attrs = _get_attrs(self.app.client_manager, parsed_args)
102+
attrs.update(
103+
self._parse_extra_properties(parsed_args.extra_properties))
101104
obj = client.create_address_scope(**attrs)
102105
display_columns, columns = _get_columns(obj)
103106
data = utils.get_item_properties(obj, columns, formatters={})
@@ -226,7 +229,7 @@ def take_action(self, parsed_args):
226229

227230
# TODO(rtheis): Use the SDK resource mapped attribute names once the
228231
# OSC minimum requirements include SDK 1.0.
229-
class SetAddressScope(command.Command):
232+
class SetAddressScope(common.NeutronCommandWithExtraArgs):
230233
_description = _("Set address scope properties")
231234

232235
def get_parser(self, prog_name):
@@ -267,6 +270,8 @@ def take_action(self, parsed_args):
267270
attrs['shared'] = True
268271
if parsed_args.no_share:
269272
attrs['shared'] = False
273+
attrs.update(
274+
self._parse_extra_properties(parsed_args.extra_properties))
270275
client.update_address_scope(obj, **attrs)
271276

272277

openstackclient/network/v2/floating_ip.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
"""IP Floating action implementations"""
1515

16-
from osc_lib.command import command
1716
from osc_lib import utils
1817
from osc_lib.utils import tags as _tag
1918

@@ -94,7 +93,8 @@ def _get_attrs(client_manager, parsed_args):
9493
return attrs
9594

9695

97-
class CreateFloatingIP(common.NetworkAndComputeShowOne):
96+
class CreateFloatingIP(common.NetworkAndComputeShowOne,
97+
common.NeutronCommandWithExtraArgs):
9898
_description = _("Create floating IP")
9999

100100
def update_parser_common(self, parser):
@@ -175,6 +175,8 @@ def update_parser_network(self, parser):
175175

176176
def take_action_network(self, client, parsed_args):
177177
attrs = _get_attrs(self.app.client_manager, parsed_args)
178+
attrs.update(
179+
self._parse_extra_properties(parsed_args.extra_properties))
178180
with common.check_missing_extension_if_error(
179181
self.app.client_manager.network, attrs):
180182
obj = client.create_ip(**attrs)
@@ -390,7 +392,7 @@ def take_action_compute(self, client, parsed_args):
390392
) for s in data))
391393

392394

393-
class SetFloatingIP(command.Command):
395+
class SetFloatingIP(common.NeutronCommandWithExtraArgs):
394396
_description = _("Set floating IP Properties")
395397

396398
def get_parser(self, prog_name):
@@ -456,6 +458,9 @@ def take_action(self, parsed_args):
456458
if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy:
457459
attrs['qos_policy_id'] = None
458460

461+
attrs.update(
462+
self._parse_extra_properties(parsed_args.extra_properties))
463+
459464
if attrs:
460465
client.update_ip(obj, **attrs)
461466

@@ -490,7 +495,7 @@ def take_action_compute(self, client, parsed_args):
490495
return (columns, data)
491496

492497

493-
class UnsetFloatingIP(command.Command):
498+
class UnsetFloatingIP(common.NeutronCommandWithExtraArgs):
494499
_description = _("Unset floating IP Properties")
495500

496501
def get_parser(self, prog_name):
@@ -526,6 +531,8 @@ def take_action(self, parsed_args):
526531
attrs['port_id'] = None
527532
if parsed_args.qos_policy:
528533
attrs['qos_policy_id'] = None
534+
attrs.update(
535+
self._parse_extra_properties(parsed_args.extra_properties))
529536

530537
if attrs:
531538
client.update_ip(obj, **attrs)

openstackclient/network/v2/floating_ip_port_forwarding.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from osc_lib import utils
2020

2121
from openstackclient.i18n import _
22+
from openstackclient.network import common
2223
from openstackclient.network import sdk_utils
2324

2425

@@ -32,7 +33,8 @@ def _get_columns(item):
3233
return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map)
3334

3435

35-
class CreateFloatingIPPortForwarding(command.ShowOne):
36+
class CreateFloatingIPPortForwarding(command.ShowOne,
37+
common.NeutronCommandWithExtraArgs):
3638
_description = _("Create floating IP port forwarding")
3739

3840
def get_parser(self, prog_name):
@@ -122,6 +124,9 @@ def take_action(self, parsed_args):
122124
if parsed_args.description is not None:
123125
attrs['description'] = parsed_args.description
124126

127+
attrs.update(
128+
self._parse_extra_properties(parsed_args.extra_properties))
129+
125130
obj = client.create_floating_ip_port_forwarding(
126131
floating_ip.id,
127132
**attrs
@@ -258,7 +263,7 @@ def take_action(self, parsed_args):
258263
) for s in data))
259264

260265

261-
class SetFloatingIPPortForwarding(command.Command):
266+
class SetFloatingIPPortForwarding(common.NeutronCommandWithExtraArgs):
262267
_description = _("Set floating IP Port Forwarding Properties")
263268

264269
def get_parser(self, prog_name):
@@ -352,6 +357,9 @@ def take_action(self, parsed_args):
352357
if parsed_args.description is not None:
353358
attrs['description'] = parsed_args.description
354359

360+
attrs.update(
361+
self._parse_extra_properties(parsed_args.extra_properties))
362+
355363
client.update_floating_ip_port_forwarding(
356364
floating_ip.id, parsed_args.port_forwarding_id, **attrs)
357365

0 commit comments

Comments
 (0)