Skip to content

Commit 456c66f

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "volume: Allow filtering volume types by properties"
2 parents d735b6c + 67bec77 commit 456c66f

3 files changed

Lines changed: 116 additions & 14 deletions

File tree

openstackclient/tests/unit/volume/v2/test_volume_type.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from unittest import mock
1616
from unittest.mock import call
1717

18+
from cinderclient import api_versions
1819
from osc_lib.cli import format_columns
1920
from osc_lib import exceptions
2021
from osc_lib import utils
@@ -327,7 +328,9 @@ def test_type_list_without_options(self):
327328
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
328329

329330
columns, data = self.cmd.take_action(parsed_args)
330-
self.volume_types_mock.list.assert_called_once_with(is_public=None)
331+
self.volume_types_mock.list.assert_called_once_with(
332+
search_opts={}, is_public=None
333+
)
331334
self.assertEqual(self.columns, columns)
332335
self.assertCountEqual(self.data, list(data))
333336

@@ -344,7 +347,9 @@ def test_type_list_with_options(self):
344347
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
345348

346349
columns, data = self.cmd.take_action(parsed_args)
347-
self.volume_types_mock.list.assert_called_once_with(is_public=True)
350+
self.volume_types_mock.list.assert_called_once_with(
351+
search_opts={}, is_public=True
352+
)
348353
self.assertEqual(self.columns_long, columns)
349354
self.assertCountEqual(self.data_long, list(data))
350355

@@ -360,7 +365,9 @@ def test_type_list_with_private_option(self):
360365
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
361366

362367
columns, data = self.cmd.take_action(parsed_args)
363-
self.volume_types_mock.list.assert_called_once_with(is_public=False)
368+
self.volume_types_mock.list.assert_called_once_with(
369+
search_opts={}, is_public=False
370+
)
364371
self.assertEqual(self.columns, columns)
365372
self.assertCountEqual(self.data, list(data))
366373

@@ -381,6 +388,60 @@ def test_type_list_with_default_option(self):
381388
self.assertEqual(self.columns, columns)
382389
self.assertCountEqual(self.data_with_default_type, list(data))
383390

391+
def test_type_list_with_property_option(self):
392+
self.app.client_manager.volume.api_version = api_versions.APIVersion(
393+
'3.52'
394+
)
395+
396+
arglist = [
397+
"--property",
398+
"multiattach=<is> True",
399+
]
400+
verifylist = [
401+
("encryption_type", False),
402+
("long", False),
403+
("is_public", None),
404+
("default", False),
405+
("properties", {"multiattach": "<is> True"}),
406+
]
407+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
408+
409+
columns, data = self.cmd.take_action(parsed_args)
410+
self.volume_types_mock.list.assert_called_once_with(
411+
search_opts={"extra_specs": {"multiattach": "<is> True"}},
412+
is_public=None,
413+
)
414+
self.assertEqual(self.columns, columns)
415+
self.assertCountEqual(self.data, list(data))
416+
417+
def test_type_list_with_property_option_pre_v352(self):
418+
self.app.client_manager.volume.api_version = api_versions.APIVersion(
419+
'3.51'
420+
)
421+
422+
arglist = [
423+
"--property",
424+
"multiattach=<is> True",
425+
]
426+
verifylist = [
427+
("encryption_type", False),
428+
("long", False),
429+
("is_public", None),
430+
("default", False),
431+
("properties", {"multiattach": "<is> True"}),
432+
]
433+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
434+
435+
exc = self.assertRaises(
436+
exceptions.CommandError,
437+
self.cmd.take_action,
438+
parsed_args,
439+
)
440+
self.assertIn(
441+
'--os-volume-api-version 3.52 or greater is required',
442+
str(exc),
443+
)
444+
384445
def test_type_list_with_encryption(self):
385446
encryption_type = volume_fakes.create_one_encryption_volume_type(
386447
attrs={'volume_type_id': self.volume_types[0].id},
@@ -426,7 +487,9 @@ def test_type_list_with_encryption(self):
426487

427488
columns, data = self.cmd.take_action(parsed_args)
428489
self.volume_encryption_types_mock.list.assert_called_once_with()
429-
self.volume_types_mock.list.assert_called_once_with(is_public=None)
490+
self.volume_types_mock.list.assert_called_once_with(
491+
search_opts={}, is_public=None
492+
)
430493
self.assertEqual(encryption_columns, columns)
431494
self.assertCountEqual(encryption_data, list(data))
432495

@@ -459,7 +522,7 @@ def test_type_set(self):
459522
verifylist = [
460523
('name', 'new_name'),
461524
('description', 'new_description'),
462-
('property', None),
525+
('properties', None),
463526
('volume_type', self.volume_type.id),
464527
]
465528
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -489,7 +552,7 @@ def test_type_set_property(self):
489552
verifylist = [
490553
('name', None),
491554
('description', None),
492-
('property', {'myprop': 'myvalue'}),
555+
('properties', {'myprop': 'myvalue'}),
493556
('volume_type', self.volume_type.id),
494557
]
495558
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -857,7 +920,7 @@ def test_type_unset(self):
857920
self.volume_type.id,
858921
]
859922
verifylist = [
860-
('property', ['property', 'multi_property']),
923+
('properties', ['property', 'multi_property']),
861924
('volume_type', self.volume_type.id),
862925
]
863926

openstackclient/volume/v2/volume_type.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import functools
1818
import logging
1919

20+
from cinderclient import api_versions
2021
from cliff import columns as cliff_columns
2122
from osc_lib.cli import format_columns
2223
from osc_lib.cli import parseractions
@@ -139,6 +140,7 @@ def get_parser(self, prog_name):
139140
'--property',
140141
metavar='<key=value>',
141142
action=parseractions.KeyValueAction,
143+
dest='properties',
142144
help=_(
143145
'Set a property on this volume type '
144146
'(repeat option to set multiple properties)'
@@ -232,11 +234,13 @@ def take_action(self, parsed_args):
232234
"type: %(e)s"
233235
)
234236
LOG.error(msg % {'project': parsed_args.project, 'e': e})
235-
if parsed_args.property:
236-
result = volume_type.set_keys(parsed_args.property)
237+
238+
if parsed_args.properties:
239+
result = volume_type.set_keys(parsed_args.properties)
237240
volume_type._info.update(
238241
{'properties': format_columns.DictColumn(result)}
239242
)
243+
240244
if (
241245
parsed_args.encryption_provider
242246
or parsed_args.encryption_cipher
@@ -261,6 +265,7 @@ def take_action(self, parsed_args):
261265
volume_type._info.update(
262266
{'encryption': format_columns.DictColumn(encryption._info)}
263267
)
268+
264269
volume_type._info.pop("os-volume-type-access:is_public", None)
265270

266271
return zip(*sorted(volume_type._info.items()))
@@ -348,6 +353,18 @@ def get_parser(self, prog_name):
348353
"(admin only)"
349354
),
350355
)
356+
parser.add_argument(
357+
'--property',
358+
metavar='<key=value>',
359+
action=parseractions.KeyValueAction,
360+
dest='properties',
361+
help=_(
362+
'Filter by a property on the volume types '
363+
'(repeat option to filter by multiple properties) '
364+
'(admin only except for user-visible extra specs) '
365+
'(supported by --os-volume-api-version 3.52 or above)'
366+
),
367+
)
351368
return parser
352369

353370
def take_action(self, parsed_args):
@@ -375,8 +392,22 @@ def take_action(self, parsed_args):
375392
if parsed_args.default:
376393
data = [volume_client.volume_types.default()]
377394
else:
395+
search_opts = {}
396+
397+
if parsed_args.properties:
398+
if volume_client.api_version < api_versions.APIVersion('3.52'):
399+
msg = _(
400+
"--os-volume-api-version 3.52 or greater is required "
401+
"to use the '--property' option"
402+
)
403+
raise exceptions.CommandError(msg)
404+
405+
# we pass this through as-is
406+
search_opts['extra_specs'] = parsed_args.properties
407+
378408
data = volume_client.volume_types.list(
379-
is_public=parsed_args.is_public
409+
search_opts=search_opts,
410+
is_public=parsed_args.is_public,
380411
)
381412

382413
formatters = {'Extra Specs': format_columns.DictColumn}
@@ -445,6 +476,7 @@ def get_parser(self, prog_name):
445476
'--property',
446477
metavar='<key=value>',
447478
action=parseractions.KeyValueAction,
479+
dest='properties',
448480
help=_(
449481
'Set a property on this volume type '
450482
'(repeat option to set multiple properties)'
@@ -555,9 +587,9 @@ def take_action(self, parsed_args):
555587
)
556588
result += 1
557589

558-
if parsed_args.property:
590+
if parsed_args.properties:
559591
try:
560-
volume_type.set_keys(parsed_args.property)
592+
volume_type.set_keys(parsed_args.properties)
561593
except Exception as e:
562594
LOG.error(_("Failed to set volume type property: %s"), e)
563595
result += 1
@@ -689,6 +721,7 @@ def get_parser(self, prog_name):
689721
'--property',
690722
metavar='<key>',
691723
action='append',
724+
dest='properties',
692725
help=_(
693726
'Remove a property from this volume type '
694727
'(repeat option to remove multiple properties)'
@@ -723,9 +756,9 @@ def take_action(self, parsed_args):
723756
)
724757

725758
result = 0
726-
if parsed_args.property:
759+
if parsed_args.properties:
727760
try:
728-
volume_type.unset_keys(parsed_args.property)
761+
volume_type.unset_keys(parsed_args.properties)
729762
except Exception as e:
730763
LOG.error(_("Failed to unset volume type property: %s"), e)
731764
result += 1
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
features:
3+
- |
4+
The ``volume type list`` command now accepts a ``--property <key>=<value>``
5+
option, allowing users to filter volume types by their extra spec
6+
properties.

0 commit comments

Comments
 (0)