From 5de1ef38445d7d9b7a81832546a064411c126a6c Mon Sep 17 00:00:00 2001 From: zhongjun Date: Tue, 12 Apr 2016 16:19:25 +0800 Subject: [PATCH] Add snapshot instances admin CLIs Support new API entry points for share snapshot instances: - share-snapshot-instance-list - share-snapshot-instance-show - share-snapshot-instance-reset-status Implements: blueprint snapshot-instances Change-Id: Ica1e81012f19926e0f1ba9cd6d8eecc5fbbf40b5 --- manilaclient/api_versions.py | 2 +- manilaclient/config.py | 5 + manilaclient/tests/functional/base.py | 33 +++++ manilaclient/tests/functional/client.py | 108 ++++++++++++++++ manilaclient/tests/functional/exceptions.py | 8 +- .../functional/test_snapshot_instances.py | 122 ++++++++++++++++++ manilaclient/tests/unit/v2/fakes.py | 47 +++++++ .../unit/v2/test_share_snapshot_instances.py | 95 ++++++++++++++ manilaclient/tests/unit/v2/test_shell.py | 36 ++++++ manilaclient/v2/client.py | 3 + manilaclient/v2/share_snapshot_instances.py | 74 +++++++++++ manilaclient/v2/shell.py | 76 +++++++++++ ...-instances-admin-api-3cf3114doe40k598.yaml | 3 + 13 files changed, 609 insertions(+), 3 deletions(-) create mode 100644 manilaclient/tests/functional/test_snapshot_instances.py create mode 100644 manilaclient/tests/unit/v2/test_share_snapshot_instances.py create mode 100644 manilaclient/v2/share_snapshot_instances.py create mode 100644 releasenotes/notes/add-snapshot-instances-admin-api-3cf3114doe40k598.yaml diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index 2af5be0d1..34d2750b8 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.17' +MAX_VERSION = '2.19' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/config.py b/manilaclient/config.py index ca5a54475..419906bff 100644 --- a/manilaclient/config.py +++ b/manilaclient/config.py @@ -125,6 +125,11 @@ share_opts = [ default="TESTDOMAIN\\Administrator", help="Username, that will be used in share access tests for " "user type of access."), + cfg.BoolOpt("run_snapshot_tests", + default=True, + help="Defines whether to run tests that use share snapshots " + "or not. Disable this feature if used driver doesn't " + "support it."), ] # 2. Generate config diff --git a/manilaclient/tests/functional/base.py b/manilaclient/tests/functional/base.py index 06fa7855d..2464b0b99 100644 --- a/manilaclient/tests/functional/base.py +++ b/manilaclient/tests/functional/base.py @@ -105,6 +105,11 @@ class BaseTestCase(base.ClientTestBase): res_id, microversion=res["microversion"]) client.wait_for_share_deletion( res_id, microversion=res["microversion"]) + elif res["type"] is "snapshot": + client.delete_snapshot( + res_id, microversion=res["microversion"]) + client.wait_for_snapshot_deletion( + res_id, microversion=res["microversion"]) else: LOG.warning("Provided unsupported resource type for " "cleanup '%s'. Skipping." % res["type"]) @@ -277,3 +282,31 @@ class BaseTestCase(base.ClientTestBase): else: cls.method_resources.insert(0, resource) return ss + + @classmethod + def create_snapshot(cls, share, name=None, description=None, + force=False, client=None, wait_for_creation=True, + cleanup_in_class=False, microversion=None): + if client is None: + client = cls.get_admin_client() + data = { + 'share': share, + 'name': name, + 'description': description, + 'force': force, + 'microversion': microversion, + } + snapshot = client.create_snapshot(**data) + resource = { + "type": "snapshot", + "id": snapshot["id"], + "client": client, + "microversion": microversion, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + if wait_for_creation: + client.wait_for_snapshot_status(snapshot['id'], 'available') + return snapshot diff --git a/manilaclient/tests/functional/client.py b/manilaclient/tests/functional/client.py index d07d22292..2b6bb032e 100644 --- a/manilaclient/tests/functional/client.py +++ b/manilaclient/tests/functional/client.py @@ -125,6 +125,8 @@ class ManilaCLIClient(base.CLIClient): func = self.is_share_network_deleted elif res_type == SHARE: func = self.is_share_deleted + elif res_type == SNAPSHOT: + func = self.is_snapshot_deleted else: raise exceptions.InvalidResource(message=res_type) @@ -692,6 +694,112 @@ class ManilaCLIClient(base.CLIClient): metadata = output_parser.details(metadata_raw) return metadata + def create_snapshot(self, share, name=None, description=None, + force=False, microversion=None): + """Creates a snapshot.""" + cmd = 'snapshot-create %(share)s ' % {'share': share} + if name is None: + name = data_utils.rand_name('autotest_snapshot_name') + cmd += '--name %s ' % name + if description is None: + description = data_utils.rand_name('autotest_snapshot_description') + cmd += '--description %s ' % description + if force: + cmd += '--force %s' % force + snapshot_raw = self.manila(cmd, microversion=microversion) + snapshot = output_parser.details(snapshot_raw) + return snapshot + + @not_found_wrapper + def get_snapshot(self, snapshot, microversion=None): + """Retrieves a snapshot by its Name or ID.""" + snapshot_raw = self.manila('snapshot-show %s' % snapshot, + microversion=microversion) + snapshot = output_parser.details(snapshot_raw) + return snapshot + + @not_found_wrapper + @forbidden_wrapper + def delete_snapshot(self, snapshot, microversion=None): + """Deletes snapshot by Names or IDs.""" + return self.manila( + "snapshot-delete %s" % snapshot, microversion=microversion) + + def list_snapshot_instances(self, snapshot_id=None, columns=None, + detailed=None, microversion=None): + """List snapshot instances.""" + cmd = 'snapshot-instance-list ' + if snapshot_id: + cmd += '--snapshot %s' % snapshot_id + if columns is not None: + cmd += ' --columns ' + columns + if detailed: + cmd += ' --detailed True ' + snapshot_instances_raw = self.manila(cmd, microversion=microversion) + snapshot_instances = utils.listing(snapshot_instances_raw) + return snapshot_instances + + def get_snapshot_instance(self, id=None, microversion=None): + """Get snapshot instance.""" + cmd = 'snapshot-instance-show %s ' % id + snapshot_instance_raw = self.manila(cmd, microversion=microversion) + snapshot_instance = output_parser.details(snapshot_instance_raw) + return snapshot_instance + + def reset_snapshot_instance(self, id=None, state=None, microversion=None): + """Reset snapshot instance status.""" + cmd = 'snapshot-instance-reset-state %s ' % id + if state: + cmd += '--state %s' % state + snapshot_instance_raw = self.manila(cmd, microversion=microversion) + snapshot_instance = utils.listing(snapshot_instance_raw) + return snapshot_instance + + def is_snapshot_deleted(self, snapshot, microversion=None): + """Indicates whether snapshot is deleted or not. + + :param snapshot: str -- Name or ID of snapshot + """ + try: + self.get_snapshot(snapshot, microversion=microversion) + return False + except tempest_lib_exc.NotFound: + return True + + def wait_for_snapshot_deletion(self, snapshot, microversion=None): + """Wait for snapshot deletion by its Name or ID. + + :param snapshot: str -- Name or ID of snapshot + """ + self.wait_for_resource_deletion( + SNAPSHOT, res_id=snapshot, interval=5, timeout=300, + microversion=microversion) + + def wait_for_snapshot_status(self, snapshot, status, microversion=None): + """Waits for a snapshot to reach a given status.""" + body = self.get_snapshot(snapshot, microversion=microversion) + snapshot_name = body['name'] + snapshot_status = body['status'] + start = int(time.time()) + + while snapshot_status != status: + time.sleep(self.build_interval) + body = self.get_snapshot(snapshot, microversion=microversion) + snapshot_status = body['status'] + + if snapshot_status == status: + return + elif 'error' in snapshot_status.lower(): + raise exceptions.SnapshotBuildErrorException(snapshot=snapshot) + + if int(time.time()) - start >= self.build_timeout: + message = ( + "Snapshot %(snapshot_name)s failed to reach %(status)s " + "status within the required time (%(timeout)s s)." % { + "snapshot_name": snapshot_name, "status": status, + "timeout": self.build_timeout}) + raise tempest_lib_exc.TimeoutException(message) + @not_found_wrapper def list_access(self, share_id, columns=None, microversion=None): """Returns list of access rules for a share. diff --git a/manilaclient/tests/functional/exceptions.py b/manilaclient/tests/functional/exceptions.py index ab78f64bf..dc9b84ee6 100644 --- a/manilaclient/tests/functional/exceptions.py +++ b/manilaclient/tests/functional/exceptions.py @@ -33,7 +33,7 @@ class InvalidData(exceptions.TempestException): class ShareTypeNotFound(exceptions.NotFound): - message = "Share type '%(share_type)s' was not found" + message = "Share type '%(share_type)s' was not found." class InvalidConfiguration(exceptions.TempestException): @@ -41,7 +41,11 @@ class InvalidConfiguration(exceptions.TempestException): class ShareBuildErrorException(exceptions.TempestException): - message = "Share %(share)s failed to build and is in ERROR status" + message = "Share %(share)s failed to build and is in ERROR status." + + +class SnapshotBuildErrorException(exceptions.TempestException): + message = "Snapshot %(snapshot)s failed to build and is in ERROR status." class AccessRuleCreateErrorException(exceptions.TempestException): diff --git a/manilaclient/tests/functional/test_snapshot_instances.py b/manilaclient/tests/functional/test_snapshot_instances.py new file mode 100644 index 000000000..2ca2ecbef --- /dev/null +++ b/manilaclient/tests/functional/test_snapshot_instances.py @@ -0,0 +1,122 @@ +# Copyright 2016 Huawei inc. +# All Rights Reserved. +# +# 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. + +import ddt +from oslo_utils import uuidutils +import testtools + +from manilaclient import config +from manilaclient.tests.functional import base +from manilaclient.tests.functional import utils + +CONF = config.CONF + + +@ddt.ddt +@testtools.skipUnless(CONF.run_snapshot_tests, + 'Snapshot tests disabled.') +@utils.skip_if_microversion_not_supported('2.19') +class SnapshotInstancesTest(base.BaseTestCase): + + @classmethod + def setUpClass(cls): + super(SnapshotInstancesTest, cls).setUpClass() + cls.share = cls.create_share( + client=cls.get_user_client(), + cleanup_in_class=True) + cls.snapshot = cls.create_snapshot(share=cls.share['id'], + client=cls.get_user_client(), + cleanup_in_class=True) + + def test_list_all_snapshot_instances(self): + snapshot_instances = self.admin_client.list_snapshot_instances() + + self.assertTrue(len(snapshot_instances) > 0) + expected_keys = ('ID', 'Snapshot ID', 'Status') + for si in snapshot_instances: + for key in expected_keys: + self.assertIn(key, si) + self.assertTrue(uuidutils.is_uuid_like(si['ID'])) + self.assertTrue(uuidutils.is_uuid_like(si['Snapshot ID'])) + + def test_list_all_snapshot_instances_details(self): + snapshot_instances = self.admin_client.list_snapshot_instances( + detailed=True) + + self.assertTrue(len(snapshot_instances) > 0) + expected_keys = ('ID', 'Snapshot ID', 'Status', 'Created_at', + 'Updated_at', 'Share_id', 'Share_instance_id', + 'Progress', 'Provider_location') + for si in snapshot_instances: + for key in expected_keys: + self.assertIn(key, si) + for key in ('ID', 'Snapshot ID', 'Share_id', 'Share_instance_id'): + self.assertTrue( + uuidutils.is_uuid_like(si[key])) + + def test_list_snapshot_instance_with_snapshot(self): + snapshot_instances = self.admin_client.list_snapshot_instances( + snapshot_id=self.snapshot['id']) + + self.assertEqual(1, len(snapshot_instances)) + expected_keys = ('ID', 'Snapshot ID', 'Status') + for si in snapshot_instances: + for key in expected_keys: + self.assertIn(key, si) + self.assertTrue(uuidutils.is_uuid_like(si['ID'])) + self.assertTrue(uuidutils.is_uuid_like(si['Snapshot ID'])) + + def test_list_snapshot_instance_with_columns(self): + snapshot_instances = self.admin_client.list_snapshot_instances( + self.snapshot['id'], columns='id,status') + + self.assertTrue(len(snapshot_instances) > 0) + expected_keys = ('Id', 'Status') + unexpected_keys = ('Snapshot ID', ) + for si in snapshot_instances: + for key in expected_keys: + self.assertIn(key, si) + for key in unexpected_keys: + self.assertNotIn(key, si) + self.assertTrue(uuidutils.is_uuid_like(si['Id'])) + + def test_get_snapshot_instance(self): + snapshot_instances = self.admin_client.list_snapshot_instances( + self.snapshot['id']) + + snapshot_instance = self.admin_client.get_snapshot_instance( + snapshot_instances[0]['ID']) + self.assertTrue(len(snapshot_instance) > 0) + expected_keys = ('id', 'snapshot_id', 'status', 'created_at', + 'updated_at', 'share_id', 'share_instance_id', + 'progress', 'provider_location') + + for key in expected_keys: + self.assertIn(key, snapshot_instance) + for key in ('id', 'snapshot_id', 'share_id', 'share_instance_id'): + self.assertTrue( + uuidutils.is_uuid_like(snapshot_instance[key])) + + def test_snapshot_instance_reset_state(self): + snapshot_instances = self.admin_client.list_snapshot_instances( + self.snapshot['id']) + self.admin_client.reset_snapshot_instance( + snapshot_instances[0]['ID'], 'error') + snapshot_instance = self.admin_client.get_snapshot_instance( + snapshot_instances[0]['ID']) + + self.assertEqual('error', snapshot_instance['status']) + self.admin_client.reset_snapshot_instance(snapshot_instance['id'], + 'available') diff --git a/manilaclient/tests/unit/v2/fakes.py b/manilaclient/tests/unit/v2/fakes.py index 35ca4046f..546c5e003 100644 --- a/manilaclient/tests/unit/v2/fakes.py +++ b/manilaclient/tests/unit/v2/fakes.py @@ -749,6 +749,53 @@ class FakeHTTPClient(fakes.FakeHTTPClient): get_types_3_share_type_access = get_types_3_os_share_type_access + fake_snapshot_instance = { + "id": "1234", + "snapshot_id": "5678", + "status": "error", + } + + def get_snapshot_instances(self, **kw): + instances = { + 'snapshot_instances': [ + self.fake_snapshot_instance, + ] + } + return (200, {}, instances) + + def get_snapshot_instances_detail(self, **kw): + instances = { + 'snapshot_instances': [ + { + 'id': '1234', + 'snapshot_id': '5679', + 'created_at': 'fake', + 'updated_at': 'fake', + 'status': 'fake', + 'share_id': 'fake', + 'share_instance_id': 'fake', + 'progress': 'fake', + 'provider_location': 'fake', + } + ] + } + return (200, {}, instances) + + def get_snapshot_instances_1234(self, **kw): + instances = {'snapshot_instance': self.fake_snapshot_instance} + return (200, {}, instances) + + def post_snapshot_instances_1234_action(self, body, **kw): + _body = None + resp = 202 + assert len(list(body)) == 1 + action = list(body)[0] + if action == 'reset_status': + assert 'status' in body.get(action) + else: + raise AssertionError("Unexpected share action: %s" % action) + return (resp, {}, _body) + def fake_create(url, body, response_key): return {'url': url, 'body': body, 'resp_key': response_key} diff --git a/manilaclient/tests/unit/v2/test_share_snapshot_instances.py b/manilaclient/tests/unit/v2/test_share_snapshot_instances.py new file mode 100644 index 000000000..0a53adb3c --- /dev/null +++ b/manilaclient/tests/unit/v2/test_share_snapshot_instances.py @@ -0,0 +1,95 @@ +# Copyright 2016 Huawei inc. +# All Rights Reserved. +# +# 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. + +import ddt +import mock + +from manilaclient import api_versions +from manilaclient import exceptions +from manilaclient import extension +from manilaclient.tests.unit import utils +from manilaclient.tests.unit.v2 import fakes +from manilaclient.v2 import share_snapshot_instances + + +extensions = [ + extension.Extension('share_snapshot_instances', share_snapshot_instances), +] +cs = fakes.FakeClient(extensions=extensions) + + +@ddt.ddt +class SnapshotInstancesTest(utils.TestCase): + + def setUp(self): + super(SnapshotInstancesTest, self).setUp() + microversion = api_versions.APIVersion("2.19") + mock_microversion = mock.Mock(api_version=microversion) + self.manager = share_snapshot_instances.ShareSnapshotInstanceManager( + api=mock_microversion) + + @ddt.data(True, False) + def test_list(self, detailed): + if detailed: + url = '/snapshot-instances/detail' + else: + url = '/snapshot-instances' + self.mock_object(self.manager, '_list', mock.Mock()) + self.manager.list(detailed=detailed) + self.manager._list.assert_called_once_with(url, 'snapshot_instances') + + @ddt.data(True, False) + def test_list_with_snapshot(self, detailed): + if detailed: + url = '/snapshot-instances/detail' + else: + url = '/snapshot-instances' + self.mock_object(self.manager, '_list', mock.Mock()) + self.manager.list(detailed=detailed, snapshot='snapshot_id') + self.manager._list.assert_called_once_with( + (url + '?snapshot_id=snapshot_id'), 'snapshot_instances',) + + def test_get(self): + self.mock_object(self.manager, '_get', mock.Mock()) + self.manager.get('fake_snapshot_instance') + self.manager._get.assert_called_once_with( + '/snapshot-instances/' + 'fake_snapshot_instance', + 'snapshot_instance') + + def test_reset_instance_state(self): + state = 'available' + + self.mock_object(self.manager, '_action', mock.Mock()) + self.manager.reset_state('fake_instance', state) + self.manager._action.assert_called_once_with( + "reset_status", 'fake_instance', {"status": state}) + + @ddt.data('get', 'list', 'reset_state') + def test_upsupported_microversion(self, method_name): + unsupported_microversions = ('1.0', '2.18') + arguments = { + 'instance': 'FAKE_INSTANCE', + } + if method_name in ('list'): + arguments.clear() + + for microversion in unsupported_microversions: + microversion = api_versions.APIVersion(microversion) + mock_microversion = mock.Mock(api_version=microversion) + manager = share_snapshot_instances.ShareSnapshotInstanceManager( + api=mock_microversion) + method = getattr(manager, method_name) + self.assertRaises(exceptions.UnsupportedVersion, + method, **arguments) diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index 4586d03f2..47e0d827b 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -1826,3 +1826,39 @@ class ShellTest(test_utils.TestCase): self.assert_called( 'POST', '/share-replicas/1234/action', body={action_name: {attr: 'xyzzyspoon!'}}) + + def test_snapshot_instance_list_all(self): + self.run_command('snapshot-instance-list') + self.assert_called('GET', '/snapshot-instances') + + def test_snapshot_instance_list_all_detail(self): + self.run_command('snapshot-instance-list --detail True') + self.assert_called('GET', '/snapshot-instances/detail') + + @mock.patch.object(cliutils, 'print_list', mock.Mock()) + def test_snapshot_instance_list_select_column(self): + self.run_command('snapshot-instance-list --columns id,status') + self.assert_called('GET', '/snapshot-instances') + cliutils.print_list.assert_called_once_with( + mock.ANY, ['Id', 'Status']) + + @mock.patch.object(shell_v2, '_find_share_snapshot', mock.Mock()) + def test_snapshot_instance_list_for_snapshot(self): + fsnapshot = type('FakeSansphot', (object,), + {'id': 'fake-snapshot-id'}) + shell_v2._find_share_snapshot.return_value = fsnapshot + cmd = 'snapshot-instance-list --snapshot %s' + self.run_command(cmd % fsnapshot.id) + + self.assert_called( + 'GET', '/snapshot-instances?snapshot_id=fake-snapshot-id') + + def test_snapshot_instance_show(self): + self.run_command('snapshot-instance-show 1234') + self.assert_called('GET', '/snapshot-instances/1234') + + def test_snapshot_instance_reset_state(self): + self.run_command('snapshot-instance-reset-state 1234') + expected = {'reset_status': {'status': 'available'}} + self.assert_called('POST', '/snapshot-instances/1234/action', + body=expected) diff --git a/manilaclient/v2/client.py b/manilaclient/v2/client.py index e5620e1b5..a44eed7f9 100644 --- a/manilaclient/v2/client.py +++ b/manilaclient/v2/client.py @@ -36,6 +36,7 @@ from manilaclient.v2 import share_instances from manilaclient.v2 import share_networks from manilaclient.v2 import share_replicas from manilaclient.v2 import share_servers +from manilaclient.v2 import share_snapshot_instances from manilaclient.v2 import share_snapshots from manilaclient.v2 import share_type_access from manilaclient.v2 import share_types @@ -220,6 +221,8 @@ class Client(object): share_instance_export_locations.ShareInstanceExportLocationManager( self)) self.share_snapshots = share_snapshots.ShareSnapshotManager(self) + self.share_snapshot_instances = ( + share_snapshot_instances.ShareSnapshotInstanceManager(self)) self.share_types = share_types.ShareTypeManager(self) self.share_type_access = share_type_access.ShareTypeAccessManager(self) diff --git a/manilaclient/v2/share_snapshot_instances.py b/manilaclient/v2/share_snapshot_instances.py new file mode 100644 index 000000000..ef77f2462 --- /dev/null +++ b/manilaclient/v2/share_snapshot_instances.py @@ -0,0 +1,74 @@ +# Copyright 2016 Huawei inc. +# All Rights Reserved. +# +# 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 manilaclient import api_versions +from manilaclient import base +from manilaclient.openstack.common.apiclient import base as common_base + + +class ShareSnapshotInstance(common_base.Resource): + """A snapshot instance is an instance of a snapshot.""" + + def __repr__(self): + return "" % self.id + + def reset_state(self, state): + """Update snapshot instance's 'status' attr.""" + self.manager.reset_state(self, state) + + +class ShareSnapshotInstanceManager(base.ManagerWithFind): + """Manage :class:`SnapshotInstances` resources.""" + resource_class = ShareSnapshotInstance + + @api_versions.wraps("2.19") + def get(self, instance): + """Get a snapshot instance. + + :param instance: either snapshot instance object or text with its ID. + :rtype: :class:`ShareSnapshotInstance` + """ + snapshot_instance_id = common_base.getid(instance) + return self._get("/snapshot-instances/%s" % snapshot_instance_id, + "snapshot_instance") + + @api_versions.wraps("2.19") + def list(self, detailed=False, snapshot=None): + """List all snapshot instances.""" + if detailed: + url = '/snapshot-instances/detail' + else: + url = '/snapshot-instances' + + if snapshot: + url += '?snapshot_id=%s' % common_base.getid(snapshot) + return self._list(url, 'snapshot_instances') + + @api_versions.wraps("2.19") + def reset_state(self, instance, state): + """Reset the 'status' attr of the snapshot instance. + + :param instance: either snapshot instance object or its UUID. + :param state: state to set the snapshot instance's 'status' attr to. + """ + return self._action("reset_status", instance, {"status": state}) + + def _action(self, action, instance, info=None, **kwargs): + """Perform a snapshot instance 'action'.""" + body = {action: info} + self.run_hooks('modify_body_for_action', body, **kwargs) + url = ('/snapshot-instances/%s/action' % + common_base.getid(instance)) + return self.api.client.post(url, body=body) diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index d906b3cec..d433e66c0 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -220,6 +220,12 @@ def _print_share_snapshot(cs, snapshot): cliutils.print_dict(info) +def _find_share_snapshot_instance(cs, snapshot_instance): + """Get a share snapshot instance by ID.""" + return apiclient_utils.find_resource( + cs.share_snapshot_instances, snapshot_instance) + + def _find_share_network(cs, share_network): """Get a share network by ID or name.""" return apiclient_utils.find_resource(cs.share_networks, share_network) @@ -1742,6 +1748,76 @@ def do_snapshot_reset_state(cs, args): snapshot.reset_state(args.state) +@api_versions.wraps("2.19") +@cliutils.arg( + '--snapshot', + metavar='', + default=None, + help='Filter results by share snapshot ID.') +@cliutils.arg( + '--columns', + metavar='', + type=str, + default=None, + help='Comma separated list of columns to be displayed ' + 'e.g. --columns "id"') +@cliutils.arg( + '--detailed', + metavar='', + default=False, + help='Show detailed information about snapshot instances.' + ' (Default=False)') +def do_snapshot_instance_list(cs, args): + """List share snapshot instances.""" + snapshot = (_find_share_snapshot(cs, args.snapshot) + if args.snapshot else None) + if args.columns is not None: + list_of_keys = _split_columns(columns=args.columns) + elif args.detailed: + list_of_keys = ['ID', 'Snapshot ID', 'Status', 'Created_at', + 'Updated_at', 'Share_id', 'Share_instance_id', + 'Progress', 'Provider_location'] + else: + list_of_keys = ['ID', 'Snapshot ID', 'Status'] + + instances = cs.share_snapshot_instances.list( + detailed=args.detailed, snapshot=snapshot) + + cliutils.print_list(instances, list_of_keys) + + +@api_versions.wraps("2.19") +@cliutils.arg( + 'snapshot_instance', + metavar='', + help='ID of the share snapshot instance.') +def do_snapshot_instance_show(cs, args): + """Show details about a share snapshot instance.""" + snapshot_instance = _find_share_snapshot_instance( + cs, args.snapshot_instance) + cliutils.print_dict(snapshot_instance._info) + + +@cliutils.arg( + 'snapshot_instance', + metavar='', + help='ID of the snapshot instance to modify.') +@cliutils.arg( + '--state', + metavar='', + default='available', + help=('Indicate which state to assign the snapshot instance. ' + 'Options include available, error, creating, deleting, ' + 'error_deleting. If no state is provided, available ' + 'will be used.')) +@api_versions.wraps("2.19") +def do_snapshot_instance_reset_state(cs, args): + """Explicitly update the state of a share snapshot instance.""" + snapshot_instance = _find_share_snapshot_instance( + cs, args.snapshot_instance) + snapshot_instance.reset_state(args.state) + + @cliutils.arg( 'share', metavar='', diff --git a/releasenotes/notes/add-snapshot-instances-admin-api-3cf3114doe40k598.yaml b/releasenotes/notes/add-snapshot-instances-admin-api-3cf3114doe40k598.yaml new file mode 100644 index 000000000..07fe45a49 --- /dev/null +++ b/releasenotes/notes/add-snapshot-instances-admin-api-3cf3114doe40k598.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add list, show, and reset-status admin commands for snapshot instances.