Metadata for Share Snapshots

Introduce MetadataCapableResource and
MetadataCapableManager to abstract away
metadata operations for a resource and
collections of resources in the manilaclient
SDK. Extend these into OSC capabilities
where appropriate. Bumps max microversion to 2.73.

In this change:
1) Shares
2) Snapshots

Depends-On: I91151792d033a4297557cd5f330053d78895eb78
Implements: bp/metadata-for-share-resources
Change-Id: I82791614b62b540eb108e99ae8ee5ce62b36b42c
This commit is contained in:
Ashley Rodriguez 2022-01-24 22:11:33 +00:00
parent 43943fdf67
commit bbfd7d5468
13 changed files with 459 additions and 89 deletions

View File

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__)
MAX_VERSION = '2.72'
MAX_VERSION = '2.73'
MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {}

View File

@ -19,6 +19,7 @@
Base utilities to build API operation managers and objects on top of.
"""
import abc
import contextlib
import copy
import hashlib
@ -354,3 +355,150 @@ class Resource(object):
def to_dict(self):
return copy.deepcopy(self._info)
class MetadataCapableResource(Resource, metaclass=abc.ABCMeta):
superresource = None
def _get_subresource_and_resource(self, superresource):
resource = self
subresource = None
superresource = superresource or self.superresource
if superresource is not None:
resource = superresource
subresource = self
return resource, subresource
def get_metadata(self, superresource=None):
"""Get metadata of a resource
:param superresource: either a parent resource object or text with
its ID. Required for sub-resources such as share export
locations which do not include a reference to the parent object
by default
"""
resource, subresource = self._get_subresource_and_resource(
superresource)
return self.manager.get_metadata(resource, subresource=subresource)
def set_metadata(self, metadata, superresource=None):
"""Set or update metadata for the resource.
:param metadata: A dictionary of key:value pairs to be set as
resource metadata
:param superresource: either a parent resource object or text with
its ID. Required for sub-resources such as share share export
locations which do not include a reference to the parent object
by default
"""
resource, subresource = self._get_subresource_and_resource(
superresource)
return self.manager.set_metadata(resource, metadata,
subresource=subresource)
def delete_metadata(self, keys, superresource=None):
"""Delete specified keys from the given resource.
:param keys: An iterable with keys of metadata items to be deleted
:param superresource: either a parent resource object or text with
its ID. Required for sub-resources such as share share export
locations which do not include a reference to the parent object
by default
"""
resource, subresource = self._get_subresource_and_resource(
superresource)
return self.manager.delete_metadata(resource, keys,
subresource=subresource)
def update_all_metadata(self, metadata, superresource=None):
"""Update all metadata for this resource.
:param metadata: A dictionary of key:value pairs of resource metadata
to be updated
:param superresource: either a parent resource object or text with
its ID. Required for sub-resources such as share share export
locations which do not include a reference to the parent object
by default
"""
resource, subresource = self._get_subresource_and_resource(
superresource)
return self.manager.update_all_metadata(resource,
metadata,
subresource=subresource)
class MetadataCapableManager(ManagerWithFind, metaclass=abc.ABCMeta):
"""Provides extended behavior to objects to handle key=value metadata."""
resource_path = None
subresource_path = None
def get_metadata(self, resource, subresource=None):
"""Get metadata of a resource.
:param resource: either resource object or text with its ID.
:param subresource: either a child resource object or text with its ID
"""
resource = getid(resource)
if subresource:
subresource = getid(subresource)
resource = f"{resource}{self.subresource_path}/{subresource}"
return self._get(f"{self.resource_path}/{resource}/metadata",
"metadata")
def set_metadata(self, resource, metadata, subresource=None):
"""Set or update metadata for resource.
:param resource: either resource object or text with its ID.
:param metadata: A dictionary of key:value pairs to be set as
resource metadata
:param subresource: either a child resource object or text with its ID
"""
body = {'metadata': metadata}
resource = getid(resource)
if subresource:
subresource = getid(subresource)
resource = f"{resource}{self.subresource_path}/{subresource}"
return self._create(f"{self.resource_path}/{resource}/metadata",
body,
"metadata")
def delete_metadata(self, resource, keys, subresource=None):
"""Delete specified keys from resource metadata.
:param resource: either resource object or text with its ID.
:param keys: An iterable with keys of metadata items to be deleted
:param subresource: either a child resource object or text with its ID
"""
resource = getid(resource)
if subresource:
subresource = getid(subresource)
resource = f"{resource}{self.subresource_path}/{subresource}"
for key in keys:
self._delete(f"{self.resource_path}/{resource}/metadata/{key}")
def update_all_metadata(self, resource, metadata, subresource=None):
"""Update all metadata of a resource.
:param resource: either resource object or text with its ID.
:param metadata: A dictionary of key:value pairs of resource metadata
to be updated
:param subresource: either a child resource object or text with its ID
"""
body = {'metadata': metadata}
resource = getid(resource)
if subresource:
subresource = getid(subresource)
resource = f"{resource}{self.subresource_path}/{subresource}"
return self._update(f"{self.resource_path}/{resource}/metadata",
body)

View File

@ -765,8 +765,7 @@ class SetShare(command.Command):
if parsed_args.property:
try:
share_client.shares.set_metadata(
share_obj.id, parsed_args.property)
share_obj.set_metadata(parsed_args.property)
except Exception as e:
LOG.error(_("Failed to set share properties "
"'%(properties)s': %(exception)s"),
@ -860,8 +859,7 @@ class UnsetShare(command.Command):
if parsed_args.property:
for key in parsed_args.property:
try:
share_client.shares.delete_metadata(
share_obj.id, [key])
share_obj.delete_metadata([key])
except Exception as e:
LOG.error(_("Failed to unset share property "
"'%(key)s': %(e)s"),
@ -1218,9 +1216,9 @@ class ShowShareProperties(command.ShowOne):
def take_action(self, parsed_args):
share_client = self.app.client_manager.share
share = apiutils.find_resource(
share_obj = apiutils.find_resource(
share_client.shares, parsed_args.share)
share_properties = share_client.shares.get_metadata(share)
share_properties = share_client.shares.get_metadata(share_obj)
return self.dict2columns(share_properties._info)

View File

@ -12,6 +12,7 @@
import logging
from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@ -20,6 +21,7 @@ from osc_lib import utils
from manilaclient import api_versions
from manilaclient.common._i18n import _
from manilaclient.common import cliutils
from manilaclient.osc import utils as oscutils
LOG = logging.getLogger(__name__)
@ -61,6 +63,15 @@ class CreateShareSnapshot(command.ShowOne):
default=False,
help=_('Wait for share snapshot creation')
)
parser.add_argument(
"--property",
metavar="<key=value>",
default={},
action=parseractions.KeyValueAction,
help=_("Set a property to this snapshot "
"(repeat option to set multiple properties)."
"Available only for microversion >= 2.73"),
)
return parser
def take_action(self, parsed_args):
@ -69,11 +80,19 @@ class CreateShareSnapshot(command.ShowOne):
share = utils.find_resource(share_client.shares,
parsed_args.share)
if share_client.api_version >= api_versions.APIVersion("2.73"):
property = parsed_args.property or {}
elif parsed_args.property:
raise exceptions.CommandError(
"Setting metadtaa is only available with manila API version "
">= 2.73")
share_snapshot = share_client.share_snapshots.create(
share=share,
force=parsed_args.force,
name=parsed_args.name or None,
description=parsed_args.description or None
description=parsed_args.description or None,
metadata=property
)
if parsed_args.wait:
if not utils.wait_for_status(
@ -188,6 +207,14 @@ class ShowShareSnapshot(command.ShowOne):
data = share_snapshot._info
data['export_locations'] = locations
# Special mapping for columns to make the output easier to read:
# 'metadata' --> 'properties'
data.update(
{
'properties':
format_columns.DictColumn(data.pop('metadata', {})),
},
)
data.pop('links', None)
return self.dict2columns(data)
@ -228,6 +255,14 @@ class SetShareSnapshot(command.Command):
"deleting, manage_starting, manage_error, "
"unmanage_starting, unmanage_error, error_deleting.")
)
parser.add_argument(
"--property",
metavar="<key=value>",
default={},
action=parseractions.KeyValueAction,
help=_("Set a property to this snapshot "
"(repeat option to set multiple properties)"),
)
return parser
def take_action(self, parsed_args):
@ -270,7 +305,15 @@ class SetShareSnapshot(command.Command):
"Failed to update snapshot status to "
"'%(status)s': %(e)s"),
{'status': parsed_args.status, 'e': e})
if parsed_args.property:
try:
share_snapshot.set_metadata(parsed_args.property)
except Exception as e:
LOG.error(_("Failed to set share snapshot properties "
"'%(properties)s': %(exception)s"),
{'properties': parsed_args.property,
'exception': e})
result += 1
if result > 0:
raise exceptions.CommandError(_("One or more of the "
"set operations failed"))
@ -297,6 +340,13 @@ class UnsetShareSnapshot(command.Command):
action='store_true',
help=_("Unset snapshot description."),
)
parser.add_argument(
'--property',
metavar='<key>',
action='append',
help=_('Remove a property from snapshot '
'(repeat option to remove multiple properties)'),
)
return parser
def take_action(self, parsed_args):
@ -321,6 +371,15 @@ class UnsetShareSnapshot(command.Command):
raise exceptions.CommandError(_(
"Failed to unset snapshot display name "
"or display description : %s" % e))
if parsed_args.property:
for key in parsed_args.property:
try:
share_snapshot.delete_metadata([key])
except Exception as e:
raise exceptions.CommandError(_(
"Failed to unset snapshot property "
"'%(key)s': %(e)s"),
{'key': key, 'e': e})
class ListShareSnapshot(command.Lister):
@ -408,6 +467,14 @@ class ListShareSnapshot(command.Lister):
default=False,
help=_("List share snapshots with details")
)
parser.add_argument(
'--property',
metavar='<key=value>',
action=parseractions.KeyValueAction,
help=_('Filter snapshots having a given metadata key=value '
'property. (repeat option to filter by multiple '
'properties)'),
)
return parser
def take_action(self, parsed_args):
@ -427,6 +494,8 @@ class ListShareSnapshot(command.Lister):
'status': parsed_args.status,
'share_id': share_id,
'usage': parsed_args.usage,
'metadata': oscutils.extract_key_value_options(
parsed_args.property),
}
if share_client.api_version >= api_versions.APIVersion("2.36"):

View File

@ -320,7 +320,7 @@ class BaseTestCase(base.ClientTestBase):
'description': description,
'public': public,
'snapshot': snapshot,
'metadata': metadata,
'metadata': metadata or {},
'microversion': microversion,
'wait': use_wait_option,
}

View File

@ -1112,7 +1112,8 @@ class TestShareSet(TestShare):
super(TestShareSet, self).setUp()
self._share = manila_fakes.FakeShare.create_one_share(
methods={"reset_state": None, "reset_task_state": None}
methods={"reset_state": None, "reset_task_state": None,
"set_metadata": None}
)
self.shares_mock.get.return_value = self._share
@ -1132,8 +1133,7 @@ class TestShareSet(TestShare):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.shares_mock.set_metadata.assert_called_with(
self._share.id,
self._share.set_metadata.assert_called_with(
{'Zorilla': 'manila'})
def test_share_set_name(self):
@ -1224,13 +1224,12 @@ class TestShareSet(TestShare):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.shares_mock.set_metadata.assert_called_with(
self._share.id,
self._share.set_metadata.assert_called_with(
{'key': ''})
# '--property' takes key=value arguments
# missing a value would raise a BadRequest
self.shares_mock.set_metadata.side_effect = exceptions.BadRequest()
self._share.set_metadata.side_effect = exceptions.BadRequest
self.assertRaises(
osc_exceptions.CommandError, self.cmd.take_action, parsed_args)
@ -1290,7 +1289,9 @@ class TestShareUnset(TestShare):
def setUp(self):
super(TestShareUnset, self).setUp()
self._share = manila_fakes.FakeShare.create_one_share()
self._share = manila_fakes.FakeShare.create_one_share(
methods={"delete_metadata": None}
)
self.shares_mock.get.return_value = self._share
# Get the command objects to test
@ -1309,8 +1310,7 @@ class TestShareUnset(TestShare):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.shares_mock.delete_metadata.assert_called_with(
self._share.id,
self._share.delete_metadata.assert_called_with(
parsed_args.property)
def test_share_unset_name(self):
@ -1376,12 +1376,11 @@ class TestShareUnset(TestShare):
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.shares_mock.delete_metadata.assert_called_with(
self._share.id,
self._share.delete_metadata.assert_called_with(
parsed_args.property)
# 404 Not Found would be raised, if property 'Manila' doesn't exist
self.shares_mock.delete_metadata.side_effect = exceptions.NotFound()
self._share.delete_metadata.side_effect = exceptions.NotFound
self.assertRaises(
osc_exceptions.CommandError, self.cmd.take_action, parsed_args)
@ -1869,7 +1868,8 @@ class TestShowShareProperties(TestShare):
attrs={
'metadata': osc_fakes.FakeResource(
info=self.properties)
}
},
methods={'get_metadata': None}
)
self.shares_mock.get.return_value = self._share
self.shares_mock.get_metadata.return_value = self._share.metadata

View File

@ -59,6 +59,9 @@ class TestShareSnapshot(manila_fakes.TestShare):
self.app.client_manager.share.share_snapshot_export_locations)
self.export_locations_mock.reset_mock()
self.app.client_manager.share.api_version = api_versions.APIVersion(
api_versions.MAX_VERSION)
class TestShareSnapshotCreate(TestShareSnapshot):
@ -105,7 +108,8 @@ class TestShareSnapshotCreate(TestShareSnapshot):
share=self.share,
force=False,
name=None,
description=None
description=None,
metadata={}
)
self.assertCountEqual(self.columns, columns)
@ -129,7 +133,8 @@ class TestShareSnapshotCreate(TestShareSnapshot):
share=self.share,
force=True,
name=None,
description=None
description=None,
metadata={}
)
self.assertCountEqual(columns, columns)
@ -155,7 +160,38 @@ class TestShareSnapshotCreate(TestShareSnapshot):
share=self.share,
force=False,
name=self.share_snapshot.name,
description=self.share_snapshot.description
description=self.share_snapshot.description,
metadata={}
)
self.assertCountEqual(self.columns, columns)
self.assertCountEqual(self.data, data)
def test_share_snapshot_create_metadata(self):
arglist = [
self.share.id,
'--name', self.share_snapshot.name,
'--description', self.share_snapshot.description,
'--property', 'Manila=zorilla',
'--property', 'Zorilla=manila'
]
verifylist = [
('share', self.share.id),
('name', self.share_snapshot.name),
('description', self.share_snapshot.description),
('property', {'Manila': 'zorilla', 'Zorilla': 'manila'}),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.snapshots_mock.create.assert_called_with(
share=self.share,
force=False,
name=self.share_snapshot.name,
description=self.share_snapshot.description,
metadata={'Manila': 'zorilla', 'Zorilla': 'manila'},
)
self.assertCountEqual(self.columns, columns)
@ -179,7 +215,8 @@ class TestShareSnapshotCreate(TestShareSnapshot):
share=self.share,
force=False,
name=None,
description=None
description=None,
metadata={}
)
self.snapshots_mock.get.assert_called_with(
@ -207,7 +244,8 @@ class TestShareSnapshotCreate(TestShareSnapshot):
share=self.share,
force=False,
name=None,
description=None
description=None,
metadata={}
)
mock_logger.error.assert_called_with(
@ -396,7 +434,9 @@ class TestShareSnapshotSet(TestShareSnapshot):
super(TestShareSnapshotSet, self).setUp()
self.share_snapshot = (
manila_fakes.FakeShareSnapshot.create_one_snapshot())
manila_fakes.FakeShareSnapshot.create_one_snapshot(
methods={"set_metadata": None}
))
self.snapshots_mock.get.return_value = self.share_snapshot
@ -458,6 +498,22 @@ class TestShareSnapshotSet(TestShareSnapshot):
parsed_args.status)
self.assertIsNone(result)
def test_set_snapshot_property(self):
arglist = [
self.share_snapshot.id,
'--property', 'Zorilla=manila',
]
verifylist = [
('snapshot', self.share_snapshot.id),
('property', {'Zorilla': 'manila'}),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.share_snapshot.set_metadata.assert_called_with(
{'Zorilla': 'manila'})
def test_set_snapshot_update_exception(self):
snapshot_name = 'snapshot-name-' + uuid.uuid4().hex
arglist = [
@ -495,6 +551,29 @@ class TestShareSnapshotSet(TestShareSnapshot):
self.cmd.take_action,
parsed_args)
def test_set_snapshot_property_exception(self):
arglist = [
'--property', 'key=',
self.share_snapshot.id,
]
verifylist = [
('property', {'key': ''}),
('snapshot', self.share_snapshot.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.share_snapshot.set_metadata.assert_called_with(
{'key': ''})
# '--property' takes key=value arguments
# missing a value would raise a BadRequest
self.share_snapshot.set_metadata.side_effect = exceptions.BadRequest
self.assertRaises(
exceptions.CommandError, self.cmd.take_action,
parsed_args)
class TestShareSnapshotUnset(TestShareSnapshot):
@ -502,7 +581,9 @@ class TestShareSnapshotUnset(TestShareSnapshot):
super(TestShareSnapshotUnset, self).setUp()
self.share_snapshot = (
manila_fakes.FakeShareSnapshot.create_one_snapshot())
manila_fakes.FakeShareSnapshot.create_one_snapshot(
methods={"delete_metadata": None}
))
self.snapshots_mock.get.return_value = self.share_snapshot
@ -544,6 +625,22 @@ class TestShareSnapshotUnset(TestShareSnapshot):
display_description=None)
self.assertIsNone(result)
def test_unset_snapshot_property(self):
arglist = [
'--property', 'Manila',
self.share_snapshot.id,
]
verifylist = [
('property', ['Manila']),
('snapshot', self.share_snapshot.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.share_snapshot.delete_metadata.assert_called_with(
parsed_args.property)
def test_unset_snapshot_name_exception(self):
arglist = [
self.share_snapshot.id,
@ -562,6 +659,27 @@ class TestShareSnapshotUnset(TestShareSnapshot):
self.cmd.take_action,
parsed_args)
def test_unset_snapshot_property_exception(self):
arglist = [
'--property', 'Manila',
self.share_snapshot.id,
]
verifylist = [
('property', ['Manila']),
('snapshot', self.share_snapshot.id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.share_snapshot.delete_metadata.assert_called_with(
parsed_args.property)
# 404 Not Found would be raised, if property 'Manila' doesn't exist
self.share_snapshot.delete_metadata.side_effect = exceptions.NotFound
self.assertRaises(
exceptions.CommandError, self.cmd.take_action, parsed_args)
class TestShareSnapshotList(TestShareSnapshot):
@ -600,9 +718,10 @@ class TestShareSnapshotList(TestShareSnapshot):
'status': None,
'share_id': None,
'usage': None,
'metadata': {},
'name~': None,
'description~': None,
'description': None
'description': None,
})
self.assertEqual(COLUMNS, columns)
@ -635,9 +754,10 @@ class TestShareSnapshotList(TestShareSnapshot):
'status': None,
'share_id': None,
'usage': None,
'metadata': {},
'name~': None,
'description~': None,
'description': None
'description': None,
})
self.assertEqual(all_tenants_list, columns)
@ -668,6 +788,7 @@ class TestShareSnapshotList(TestShareSnapshot):
'status': None,
'share_id': None,
'usage': None,
'metadata': {},
'name~': None,
'description~': None,
'description': None
@ -724,6 +845,7 @@ class TestShareSnapshotList(TestShareSnapshot):
'status': None,
'share_id': self.share.id,
'usage': None,
'metadata': {},
'name~': None,
'description~': None,
'description': None

View File

@ -1160,6 +1160,24 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
}]}
return (200, {}, access_list)
def delete_snapshots_1234_metadata_test_key(self, **kw):
return (204, {}, None)
def delete_snapshots_1234_metadata_key1(self, **kw):
return (204, {}, None)
def delete_snapshots_1234_metadata_key2(self, **kw):
return (204, {}, None)
def post_snapshots_1234_metadata(self, **kw):
return (204, {}, {'metadata': {'test_key': 'test_value'}})
def put_snapshots_1234_metadata(self, **kw):
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
def get_snapshots_1234_metadata(self, **kw):
return (200, {}, {"metadata": {"key1": "val1", "key2": "val2"}})
def post_snapshot_instances_1234_action(self, body, **kw):
_body = None
resp = 202

View File

@ -215,3 +215,30 @@ class ShareSnapshotsTest(utils.TestCase):
def test_access_list(self):
cs.share_snapshots.access_list(1234)
cs.assert_called('GET', '/snapshots/1234/access-list')
def test_get_metadata(self):
cs.share_snapshots.get_metadata(1234)
cs.assert_called('GET', '/snapshots/1234/metadata')
def test_set_metadata(self):
cs.share_snapshots.set_metadata(1234, {'k1': 'v2'})
cs.assert_called('POST', '/snapshots/1234/metadata',
{'metadata': {'k1': 'v2'}})
@ddt.data(
type('SnapshotUUID', (object, ), {'uuid': '1234'}),
type('SnapshotID', (object, ), {'id': '1234'}),
'1234')
def test_delete_metadata(self, snapshot):
keys = ['key1']
cs.share_snapshots.delete_metadata(snapshot, keys)
cs.assert_called('DELETE', '/snapshots/1234/metadata/key1')
@ddt.data(
type('SnapshotUUID', (object, ), {'uuid': '1234'}),
type('SnapshotID', (object, ), {'id': '1234'}),
'1234')
def test_metadata_update_all(self, snapshot):
cs.share_snapshots.update_all_metadata(snapshot, {'k1': 'v1'})
cs.assert_called('PUT', '/snapshots/1234/metadata',
{'metadata': {'k1': 'v1'}})

View File

@ -19,7 +19,8 @@ from manilaclient import base
from manilaclient.common import constants
class ShareSnapshot(base.Resource):
class ShareSnapshot(base.MetadataCapableResource):
"""Represent a snapshot of a share."""
def __repr__(self):
@ -57,11 +58,13 @@ class ShareSnapshot(base.Resource):
return self.manager.access_list(self)
class ShareSnapshotManager(base.ManagerWithFind):
class ShareSnapshotManager(base.MetadataCapableManager):
"""Manage :class:`ShareSnapshot` resources."""
resource_class = ShareSnapshot
resource_path = '/snapshots'
def create(self, share, force=False, name=None, description=None):
def _do_create(self, share, force=False, name=None, description=None,
metadata=None):
"""Create a snapshot of the given share.
:param share_id: The ID of the share to snapshot.
@ -69,14 +72,27 @@ class ShareSnapshotManager(base.ManagerWithFind):
share is busy. Default is False.
:param name: Name of the snapshot
:param description: Description of the snapshot
:param metadata: dict - optional metadata to set on share creation
:rtype: :class:`ShareSnapshot`
"""
metadata = metadata if metadata is not None else dict()
body = {'snapshot': {'share_id': base.getid(share),
'force': force,
'name': name,
'description': description}}
'description': description,
'metadata': metadata}}
return self._create('/snapshots', body, 'snapshot')
@api_versions.wraps("2.0", "2.72")
def create(self, share, force=False, name=None, description=None):
return self._do_create(share, force, name, description)
@api_versions.wraps("2.73")
def create(self, share, force=False, name=None, description=None,# noqa F811
metadata=None):
return self._do_create(share, force, name, description, metadata)
@api_versions.wraps("2.12")
def manage(self, share, provider_location,
driver_options=None,

View File

@ -27,7 +27,8 @@ from manilaclient import exceptions
from manilaclient.v2 import share_instances
class Share(base.Resource):
class Share(base.MetadataCapableResource):
"""A share is an extra block level storage to the OpenStack instances."""
def __repr__(self):
return "<Share: %s>" % self.id
@ -87,10 +88,6 @@ class Share(base.Resource):
"""Get access list from a share."""
return self.manager.access_list(self)
def update_all_metadata(self, metadata):
"""Update all metadata of this share."""
return self.manager.update_all_metadata(self, metadata)
def reset_state(self, state):
"""Update the share with the provided state."""
self.manager.reset_state(self, state)
@ -120,9 +117,10 @@ class Share(base.Resource):
self.manager.restore(self)
class ShareManager(base.ManagerWithFind):
class ShareManager(base.MetadataCapableManager):
"""Manage :class:`Share` resources."""
resource_class = Share
resource_path = '/shares'
def create(self, share_proto, size, snapshot_id=None, name=None,
description=None, metadata=None, share_network=None,
@ -659,45 +657,6 @@ class ShareManager(base.ManagerWithFind):
def access_list(self, share): # noqa
return self._do_access_list(share, "access_list")
def get_metadata(self, share):
"""Get metadata of a share.
:param share: either share object or text with its ID.
"""
return self._get("/shares/%s/metadata" % base.getid(share),
"metadata")
def set_metadata(self, share, metadata):
"""Set or update metadata for share.
:param share: either share object or text with its ID.
:param metadata: A list of keys to be set.
"""
body = {'metadata': metadata}
return self._create("/shares/%s/metadata" % base.getid(share),
body, "metadata")
def delete_metadata(self, share, keys):
"""Delete specified keys from shares metadata.
:param share: either share object or text with its ID.
:param keys: A list of keys to be removed.
"""
share_id = base.getid(share)
for key in keys:
self._delete("/shares/%(share_id)s/metadata/%(key)s" % {
'share_id': share_id, 'key': key})
def update_all_metadata(self, share, metadata):
"""Update all metadata of a share.
:param share: either share object or text with its ID.
:param metadata: A list of keys to be updated.
"""
body = {'metadata': metadata}
return self._update("/shares/%s/metadata" % base.getid(share),
body)
def _action(self, action, share, info=None, **kwargs):
"""Perform a share 'action'.

View File

@ -1408,16 +1408,16 @@ def do_share_server_migration_get_progress(cs, args):
metavar='<key=value>',
nargs='+',
default=[],
help='Metadata to set or unset (key is only necessary on unset).')
help='Metadata to set or unset (only key is necessary to unset).')
def do_metadata(cs, args):
"""Set or delete metadata on a share."""
share = _find_share(cs, args.share)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.shares.set_metadata(share, metadata)
share.set_metadata(metadata)
elif args.action == 'unset':
cs.shares.delete_metadata(share, sorted(list(metadata), reverse=True))
share.delete_metadata(sorted(list(metadata), reverse=True))
@cliutils.arg(
@ -1427,7 +1427,7 @@ def do_metadata(cs, args):
def do_metadata_show(cs, args):
"""Show metadata of given share."""
share = _find_share(cs, args.share)
metadata = cs.shares.get_metadata(share)._info
metadata = share.get_metadata()._info
cliutils.print_dict(metadata, 'Property')
@ -2826,6 +2826,14 @@ def do_share_instance_export_location_show(cs, args):
default=None,
help='Filter results matching a share snapshot description pattern. '
'Available only for microversion >= 2.36.')
@cliutils.arg(
'--metadata',
metavar='<key=value>',
type=str,
default=None,
nargs='*',
help='Filters results by a metadata key and value. OPTIONAL: '
'Default=None, Available only for microversion >= 2.73. ')
def do_snapshot_list(cs, args):
"""List all the snapshots."""
all_projects = int(
@ -2852,6 +2860,7 @@ def do_snapshot_list(cs, args):
'status': args.status,
'share_id': share.id,
'usage': args.usage,
'metadata': _extract_metadata(args),
}
if cs.api_version.matches(api_versions.APIVersion("2.36"),
api_versions.APIVersion()):
@ -5218,7 +5227,7 @@ def do_type_delete(cs, args):
metavar='<key=value>',
nargs='*',
default=None,
help='Extra_specs to set or unset (key is only necessary on unset).')
help='Extra_specs to set or unset (only key is necessary to unset).')
def do_type_key(cs, args):
"""Set or unset extra_spec for a share type (Admin only)."""
stype = _find_share_type(cs, args.stype)
@ -5455,7 +5464,7 @@ def do_share_group_type_delete(cs, args):
metavar='<key=value>',
nargs='*',
default=None,
help='Group specs to set or unset (key is only necessary on unset).')
help='Group specs to set or unset (only key is necessary to unset).')
@cliutils.service_type('sharev2')
def do_share_group_type_key(cs, args):
"""Set or unset group_spec for a share group type (Admin only)."""
@ -6438,11 +6447,11 @@ def do_share_replica_export_location_list(cs, args):
@cliutils.arg(
'replica',
metavar='<replica>',
help='Name or ID of the share instance.')
help='Name or ID of the share replica.')
@cliutils.arg(
'export_location',
metavar='<export_location>',
help='ID of the share instance export location.')
help='ID of the share replica export location.')
def do_share_replica_export_location_show(cs, args):
"""Show details of a share replica's export location."""
replica = _find_share_replica(cs, args.replica)

View File

@ -0,0 +1,4 @@
features:
- |
Adds support to set snapshot property on snapshot create, filter list on
snapshot property, and snapshot property set and unset. Only in OSC.