Merge "volume: Allow filtering volume types by properties"
This commit is contained in:
commit
456c66ff63
openstackclient
releasenotes/notes
@ -15,6 +15,7 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
from unittest.mock import call
|
from unittest.mock import call
|
||||||
|
|
||||||
|
from cinderclient import api_versions
|
||||||
from osc_lib.cli import format_columns
|
from osc_lib.cli import format_columns
|
||||||
from osc_lib import exceptions
|
from osc_lib import exceptions
|
||||||
from osc_lib import utils
|
from osc_lib import utils
|
||||||
@ -327,7 +328,9 @@ class TestTypeList(TestType):
|
|||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
self.volume_types_mock.list.assert_called_once_with(is_public=None)
|
self.volume_types_mock.list.assert_called_once_with(
|
||||||
|
search_opts={}, is_public=None
|
||||||
|
)
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.data, list(data))
|
self.assertCountEqual(self.data, list(data))
|
||||||
|
|
||||||
@ -344,7 +347,9 @@ class TestTypeList(TestType):
|
|||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
self.volume_types_mock.list.assert_called_once_with(is_public=True)
|
self.volume_types_mock.list.assert_called_once_with(
|
||||||
|
search_opts={}, is_public=True
|
||||||
|
)
|
||||||
self.assertEqual(self.columns_long, columns)
|
self.assertEqual(self.columns_long, columns)
|
||||||
self.assertCountEqual(self.data_long, list(data))
|
self.assertCountEqual(self.data_long, list(data))
|
||||||
|
|
||||||
@ -360,7 +365,9 @@ class TestTypeList(TestType):
|
|||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
self.volume_types_mock.list.assert_called_once_with(is_public=False)
|
self.volume_types_mock.list.assert_called_once_with(
|
||||||
|
search_opts={}, is_public=False
|
||||||
|
)
|
||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.data, list(data))
|
self.assertCountEqual(self.data, list(data))
|
||||||
|
|
||||||
@ -381,6 +388,60 @@ class TestTypeList(TestType):
|
|||||||
self.assertEqual(self.columns, columns)
|
self.assertEqual(self.columns, columns)
|
||||||
self.assertCountEqual(self.data_with_default_type, list(data))
|
self.assertCountEqual(self.data_with_default_type, list(data))
|
||||||
|
|
||||||
|
def test_type_list_with_property_option(self):
|
||||||
|
self.app.client_manager.volume.api_version = api_versions.APIVersion(
|
||||||
|
'3.52'
|
||||||
|
)
|
||||||
|
|
||||||
|
arglist = [
|
||||||
|
"--property",
|
||||||
|
"multiattach=<is> True",
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("encryption_type", False),
|
||||||
|
("long", False),
|
||||||
|
("is_public", None),
|
||||||
|
("default", False),
|
||||||
|
("properties", {"multiattach": "<is> True"}),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
|
self.volume_types_mock.list.assert_called_once_with(
|
||||||
|
search_opts={"extra_specs": {"multiattach": "<is> True"}},
|
||||||
|
is_public=None,
|
||||||
|
)
|
||||||
|
self.assertEqual(self.columns, columns)
|
||||||
|
self.assertCountEqual(self.data, list(data))
|
||||||
|
|
||||||
|
def test_type_list_with_property_option_pre_v352(self):
|
||||||
|
self.app.client_manager.volume.api_version = api_versions.APIVersion(
|
||||||
|
'3.51'
|
||||||
|
)
|
||||||
|
|
||||||
|
arglist = [
|
||||||
|
"--property",
|
||||||
|
"multiattach=<is> True",
|
||||||
|
]
|
||||||
|
verifylist = [
|
||||||
|
("encryption_type", False),
|
||||||
|
("long", False),
|
||||||
|
("is_public", None),
|
||||||
|
("default", False),
|
||||||
|
("properties", {"multiattach": "<is> True"}),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
exc = self.assertRaises(
|
||||||
|
exceptions.CommandError,
|
||||||
|
self.cmd.take_action,
|
||||||
|
parsed_args,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
'--os-volume-api-version 3.52 or greater is required',
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
|
||||||
def test_type_list_with_encryption(self):
|
def test_type_list_with_encryption(self):
|
||||||
encryption_type = volume_fakes.create_one_encryption_volume_type(
|
encryption_type = volume_fakes.create_one_encryption_volume_type(
|
||||||
attrs={'volume_type_id': self.volume_types[0].id},
|
attrs={'volume_type_id': self.volume_types[0].id},
|
||||||
@ -426,7 +487,9 @@ class TestTypeList(TestType):
|
|||||||
|
|
||||||
columns, data = self.cmd.take_action(parsed_args)
|
columns, data = self.cmd.take_action(parsed_args)
|
||||||
self.volume_encryption_types_mock.list.assert_called_once_with()
|
self.volume_encryption_types_mock.list.assert_called_once_with()
|
||||||
self.volume_types_mock.list.assert_called_once_with(is_public=None)
|
self.volume_types_mock.list.assert_called_once_with(
|
||||||
|
search_opts={}, is_public=None
|
||||||
|
)
|
||||||
self.assertEqual(encryption_columns, columns)
|
self.assertEqual(encryption_columns, columns)
|
||||||
self.assertCountEqual(encryption_data, list(data))
|
self.assertCountEqual(encryption_data, list(data))
|
||||||
|
|
||||||
@ -459,7 +522,7 @@ class TestTypeSet(TestType):
|
|||||||
verifylist = [
|
verifylist = [
|
||||||
('name', 'new_name'),
|
('name', 'new_name'),
|
||||||
('description', 'new_description'),
|
('description', 'new_description'),
|
||||||
('property', None),
|
('properties', None),
|
||||||
('volume_type', self.volume_type.id),
|
('volume_type', self.volume_type.id),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
@ -489,7 +552,7 @@ class TestTypeSet(TestType):
|
|||||||
verifylist = [
|
verifylist = [
|
||||||
('name', None),
|
('name', None),
|
||||||
('description', None),
|
('description', None),
|
||||||
('property', {'myprop': 'myvalue'}),
|
('properties', {'myprop': 'myvalue'}),
|
||||||
('volume_type', self.volume_type.id),
|
('volume_type', self.volume_type.id),
|
||||||
]
|
]
|
||||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||||
@ -857,7 +920,7 @@ class TestTypeUnset(TestType):
|
|||||||
self.volume_type.id,
|
self.volume_type.id,
|
||||||
]
|
]
|
||||||
verifylist = [
|
verifylist = [
|
||||||
('property', ['property', 'multi_property']),
|
('properties', ['property', 'multi_property']),
|
||||||
('volume_type', self.volume_type.id),
|
('volume_type', self.volume_type.id),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from cinderclient import api_versions
|
||||||
from cliff import columns as cliff_columns
|
from cliff import columns as cliff_columns
|
||||||
from osc_lib.cli import format_columns
|
from osc_lib.cli import format_columns
|
||||||
from osc_lib.cli import parseractions
|
from osc_lib.cli import parseractions
|
||||||
@ -139,6 +140,7 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
'--property',
|
'--property',
|
||||||
metavar='<key=value>',
|
metavar='<key=value>',
|
||||||
action=parseractions.KeyValueAction,
|
action=parseractions.KeyValueAction,
|
||||||
|
dest='properties',
|
||||||
help=_(
|
help=_(
|
||||||
'Set a property on this volume type '
|
'Set a property on this volume type '
|
||||||
'(repeat option to set multiple properties)'
|
'(repeat option to set multiple properties)'
|
||||||
@ -232,11 +234,13 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
"type: %(e)s"
|
"type: %(e)s"
|
||||||
)
|
)
|
||||||
LOG.error(msg % {'project': parsed_args.project, 'e': e})
|
LOG.error(msg % {'project': parsed_args.project, 'e': e})
|
||||||
if parsed_args.property:
|
|
||||||
result = volume_type.set_keys(parsed_args.property)
|
if parsed_args.properties:
|
||||||
|
result = volume_type.set_keys(parsed_args.properties)
|
||||||
volume_type._info.update(
|
volume_type._info.update(
|
||||||
{'properties': format_columns.DictColumn(result)}
|
{'properties': format_columns.DictColumn(result)}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
parsed_args.encryption_provider
|
parsed_args.encryption_provider
|
||||||
or parsed_args.encryption_cipher
|
or parsed_args.encryption_cipher
|
||||||
@ -261,6 +265,7 @@ class CreateVolumeType(command.ShowOne):
|
|||||||
volume_type._info.update(
|
volume_type._info.update(
|
||||||
{'encryption': format_columns.DictColumn(encryption._info)}
|
{'encryption': format_columns.DictColumn(encryption._info)}
|
||||||
)
|
)
|
||||||
|
|
||||||
volume_type._info.pop("os-volume-type-access:is_public", None)
|
volume_type._info.pop("os-volume-type-access:is_public", None)
|
||||||
|
|
||||||
return zip(*sorted(volume_type._info.items()))
|
return zip(*sorted(volume_type._info.items()))
|
||||||
@ -348,6 +353,18 @@ class ListVolumeType(command.Lister):
|
|||||||
"(admin only)"
|
"(admin only)"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--property',
|
||||||
|
metavar='<key=value>',
|
||||||
|
action=parseractions.KeyValueAction,
|
||||||
|
dest='properties',
|
||||||
|
help=_(
|
||||||
|
'Filter by a property on the volume types '
|
||||||
|
'(repeat option to filter by multiple properties) '
|
||||||
|
'(admin only except for user-visible extra specs) '
|
||||||
|
'(supported by --os-volume-api-version 3.52 or above)'
|
||||||
|
),
|
||||||
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -375,8 +392,22 @@ class ListVolumeType(command.Lister):
|
|||||||
if parsed_args.default:
|
if parsed_args.default:
|
||||||
data = [volume_client.volume_types.default()]
|
data = [volume_client.volume_types.default()]
|
||||||
else:
|
else:
|
||||||
|
search_opts = {}
|
||||||
|
|
||||||
|
if parsed_args.properties:
|
||||||
|
if volume_client.api_version < api_versions.APIVersion('3.52'):
|
||||||
|
msg = _(
|
||||||
|
"--os-volume-api-version 3.52 or greater is required "
|
||||||
|
"to use the '--property' option"
|
||||||
|
)
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
|
# we pass this through as-is
|
||||||
|
search_opts['extra_specs'] = parsed_args.properties
|
||||||
|
|
||||||
data = volume_client.volume_types.list(
|
data = volume_client.volume_types.list(
|
||||||
is_public=parsed_args.is_public
|
search_opts=search_opts,
|
||||||
|
is_public=parsed_args.is_public,
|
||||||
)
|
)
|
||||||
|
|
||||||
formatters = {'Extra Specs': format_columns.DictColumn}
|
formatters = {'Extra Specs': format_columns.DictColumn}
|
||||||
@ -445,6 +476,7 @@ class SetVolumeType(command.Command):
|
|||||||
'--property',
|
'--property',
|
||||||
metavar='<key=value>',
|
metavar='<key=value>',
|
||||||
action=parseractions.KeyValueAction,
|
action=parseractions.KeyValueAction,
|
||||||
|
dest='properties',
|
||||||
help=_(
|
help=_(
|
||||||
'Set a property on this volume type '
|
'Set a property on this volume type '
|
||||||
'(repeat option to set multiple properties)'
|
'(repeat option to set multiple properties)'
|
||||||
@ -555,9 +587,9 @@ class SetVolumeType(command.Command):
|
|||||||
)
|
)
|
||||||
result += 1
|
result += 1
|
||||||
|
|
||||||
if parsed_args.property:
|
if parsed_args.properties:
|
||||||
try:
|
try:
|
||||||
volume_type.set_keys(parsed_args.property)
|
volume_type.set_keys(parsed_args.properties)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(_("Failed to set volume type property: %s"), e)
|
LOG.error(_("Failed to set volume type property: %s"), e)
|
||||||
result += 1
|
result += 1
|
||||||
@ -689,6 +721,7 @@ class UnsetVolumeType(command.Command):
|
|||||||
'--property',
|
'--property',
|
||||||
metavar='<key>',
|
metavar='<key>',
|
||||||
action='append',
|
action='append',
|
||||||
|
dest='properties',
|
||||||
help=_(
|
help=_(
|
||||||
'Remove a property from this volume type '
|
'Remove a property from this volume type '
|
||||||
'(repeat option to remove multiple properties)'
|
'(repeat option to remove multiple properties)'
|
||||||
@ -723,9 +756,9 @@ class UnsetVolumeType(command.Command):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result = 0
|
result = 0
|
||||||
if parsed_args.property:
|
if parsed_args.properties:
|
||||||
try:
|
try:
|
||||||
volume_type.unset_keys(parsed_args.property)
|
volume_type.unset_keys(parsed_args.properties)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(_("Failed to unset volume type property: %s"), e)
|
LOG.error(_("Failed to unset volume type property: %s"), e)
|
||||||
result += 1
|
result += 1
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
The ``volume type list`` command now accepts a ``--property <key>=<value>``
|
||||||
|
option, allowing users to filter volume types by their extra spec
|
||||||
|
properties.
|
Loading…
x
Reference in New Issue
Block a user