diff --git a/contrib/tempest/tempest/api/share/admin/test_admin_actions.py b/contrib/tempest/tempest/api/share/admin/test_admin_actions.py index 4633909344..97a698e3e8 100644 --- a/contrib/tempest/tempest/api/share/admin/test_admin_actions.py +++ b/contrib/tempest/tempest/api/share/admin/test_admin_actions.py @@ -30,6 +30,9 @@ class AdminActionsTest(base.BaseSharesAdminTest): cls.states = ["error", "available"] cls.bad_status = "error_deleting" cls.sh = cls.create_share() + cls.sh_instance = ( + cls.shares_client.get_instances_of_share(cls.sh["id"])[0] + ) if CONF.share.run_snapshot_tests: cls.sn = cls.create_snapshot_wait_for_active(cls.sh["id"]) @@ -39,6 +42,14 @@ class AdminActionsTest(base.BaseSharesAdminTest): self.shares_client.reset_state(self.sh["id"], status=status) self.shares_client.wait_for_share_status(self.sh["id"], status) + @test.attr(type=["gate", ]) + def test_reset_share_instance_state(self): + id = self.sh_instance["id"] + for status in self.states: + self.shares_client.reset_state( + id, s_type="share_instances", status=status) + self.shares_client.wait_for_share_instance_status(id, status) + @test.attr(type=["gate", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") @@ -63,6 +74,29 @@ class AdminActionsTest(base.BaseSharesAdminTest): self.shares_client.force_delete(share["id"]) self.shares_client.wait_for_resource_deletion(share_id=share["id"]) + @test.attr(type=["gate", ]) + def test_force_delete_share_instance(self): + share = self.create_share(cleanup_in_class=False) + instances = self.shares_client.get_instances_of_share(share["id"]) + # Check that instance was created + self.assertEqual(1, len(instances)) + + instance = instances[0] + + # Change status from 'available' to 'error_deleting' + self.shares_client.reset_state( + instance["id"], s_type="share_instances", status=self.bad_status) + + # Check that status was changed + check_status = self.shares_client.get_share_instance(instance["id"]) + self.assertEqual(self.bad_status, check_status["status"]) + + # Share with status 'error_deleting' should be deleted + self.shares_client.force_delete( + instance["id"], s_type="share_instances") + self.shares_client.wait_for_resource_deletion( + share_instance_id=instance["id"]) + @test.attr(type=["gate", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") diff --git a/contrib/tempest/tempest/api/share/admin/test_admin_actions_negative.py b/contrib/tempest/tempest/api/share/admin/test_admin_actions_negative.py index bf03b37b8c..2796cfa1a6 100644 --- a/contrib/tempest/tempest/api/share/admin/test_admin_actions_negative.py +++ b/contrib/tempest/tempest/api/share/admin/test_admin_actions_negative.py @@ -30,6 +30,9 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): def resource_setup(cls): super(AdminActionsNegativeTest, cls).resource_setup() cls.sh = cls.create_share() + cls.sh_instance = ( + cls.shares_client.get_instances_of_share(cls.sh["id"])[0] + ) if CONF.share.run_snapshot_tests: cls.sn = cls.create_snapshot_wait_for_active(cls.sh["id"]) cls.member_shares_client = clients.Manager().shares_client @@ -39,6 +42,11 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): self.assertRaises(lib_exc.NotFound, self.shares_client.reset_state, "fake") + @test.attr(type=["gate", "negative", ]) + def test_reset_nonexistent_share_instance_state(self): + self.assertRaises(lib_exc.NotFound, self.shares_client.reset_state, + "fake", s_type="share_instances") + @test.attr(type=["gate", "negative", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") @@ -52,6 +60,16 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): self.shares_client.reset_state, self.sh["id"], status="fake") + @test.attr(type=["gate", "negative", ]) + def test_reset_share_instance_state_to_unacceptable_state(self): + self.assertRaises( + lib_exc.BadRequest, + self.shares_client.reset_state, + self.sh_instance["id"], + s_type="share_instances", + status="fake" + ) + @test.attr(type=["gate", "negative", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") @@ -67,6 +85,13 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): self.member_shares_client.reset_state, self.sh["id"]) + @test.attr(type=["gate", "negative", ]) + def test_try_reset_share_instance_state_with_member(self): + # Even if member from another tenant, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.reset_state, + self.sh_instance["id"], s_type="share_instances") + @test.attr(type=["gate", "negative", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") @@ -81,6 +106,13 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): self.assertRaises(lib_exc.NotFound, self.shares_client.force_delete, "fake") + @test.attr(type=["gate", "negative", ]) + def test_force_delete_nonexistent_share_instance(self): + self.assertRaises(lib_exc.NotFound, + self.shares_client.force_delete, + "fake", + s_type="share_instances") + @test.attr(type=["gate", "negative", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") @@ -97,6 +129,13 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): self.member_shares_client.force_delete, self.sh["id"]) + @test.attr(type=["gate", "negative", ]) + def test_try_force_delete_share_instance_with_member(self): + # If a non-admin tries to do force_delete, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.force_delete, + self.sh_instance["id"], s_type="share_instances") + @test.attr(type=["gate", "negative", ]) @testtools.skipUnless(CONF.share.run_snapshot_tests, "Snapshot tests are disabled.") @@ -105,3 +144,24 @@ class AdminActionsNegativeTest(base.BaseSharesAdminTest): self.assertRaises(lib_exc.Forbidden, self.member_shares_client.force_delete, self.sn["id"], s_type="snapshots") + + @test.attr(type=["gate", "negative", ]) + def test_try_get_share_instance_with_member(self): + # If a non-admin tries to get instance, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.get_share_instance, + self.sh_instance["id"]) + + @test.attr(type=["gate", "negative", ]) + def test_try_list_share_instance_with_member(self): + # If a non-admin tries to list instances, it should be unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.list_share_instances) + + @test.attr(type=["gate", "negative", ]) + def test_try_get_instances_of_share_with_member(self): + # If a non-admin tries to list instances of given share, it should be + # unauthorized + self.assertRaises(lib_exc.Forbidden, + self.member_shares_client.get_instances_of_share, + self.sh['id']) diff --git a/contrib/tempest/tempest/config_share.py b/contrib/tempest/tempest/config_share.py index 4bd8933088..c3927c6188 100644 --- a/contrib/tempest/tempest/config_share.py +++ b/contrib/tempest/tempest/config_share.py @@ -36,7 +36,7 @@ ShareGroup = [ help="The minimum api microversion is configured to be the " "value of the minimum microversion supported by Manila."), cfg.StrOpt("max_api_microversion", - default="1.3", + default="1.4", help="The maximum api microversion is configured to be the " "value of the latest microversion supported by Manila."), cfg.StrOpt("region", diff --git a/contrib/tempest/tempest/services/share/json/shares_client.py b/contrib/tempest/tempest/services/share/json/shares_client.py index 03c79d218c..b72114c6a8 100644 --- a/contrib/tempest/tempest/services/share/json/shares_client.py +++ b/contrib/tempest/tempest/services/share/json/shares_client.py @@ -48,6 +48,9 @@ class SharesClient(rest_client.RestClient): self.build_timeout = CONF.share.build_timeout self.API_MICROVERSIONS_HEADER = 'x-openstack-manila-api-version' + def _get_version_dict(self, version): + return {self.API_MICROVERSIONS_HEADER: version} + def send_microversion_request(self, version=None): """Prepare and send the HTTP GET Request to the base URL. @@ -144,6 +147,27 @@ class SharesClient(rest_client.RestClient): self.expected_success(200, resp.status) return self._parse_resp(body) + def get_instances_of_share(self, share_id): + resp, body = self.get("shares/%s/instances" % share_id, + headers=self._get_version_dict('1.4'), + extra_headers=True) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def list_share_instances(self): + resp, body = self.get("share_instances", + headers=self._get_version_dict('1.4'), + extra_headers=True) + self.expected_success(200, resp.status) + return self._parse_resp(body) + + def get_share_instance(self, instance_id): + resp, body = self.get("share_instances/%s" % instance_id, + headers=self._get_version_dict('1.4'), + extra_headers=True) + self.expected_success(200, resp.status) + return self._parse_resp(body) + def create_access_rule(self, share_id, access_type="ip", access_to="0.0.0.0", access_level=None): post_body = { @@ -262,6 +286,28 @@ class SharesClient(rest_client.RestClient): (share_name, status, self.build_timeout)) raise exceptions.TimeoutException(message) + def wait_for_share_instance_status(self, instance_id, status): + """Waits for a share to reach a given status.""" + body = self.get_share_instance(instance_id) + instance_status = body['status'] + start = int(time.time()) + + while instance_status != status: + time.sleep(self.build_interval) + body = self.get_share(instance_id) + instance_status = body['status'] + if instance_status == status: + return + elif 'error' in instance_status.lower(): + raise share_exceptions.\ + ShareInstanceBuildErrorException(id=instance_id) + + if int(time.time()) - start >= self.build_timeout: + message = ('Share instance %s failed to reach %s status within' + ' the required time (%s s).' % + (instance_id, status, self.build_timeout)) + raise exceptions.TimeoutException(message) + def wait_for_snapshot_status(self, snapshot_id, status): """Waits for a snapshot to reach a given status.""" body = self.get_snapshot(snapshot_id) @@ -375,6 +421,9 @@ class SharesClient(rest_client.RestClient): else: return self._is_resource_deleted( self.get_share, kwargs.get("share_id")) + elif "share_instance_id" in kwargs: + return self._is_resource_deleted( + self.get_share_instance, kwargs.get("share_instance_id")) elif "snapshot_id" in kwargs: return self._is_resource_deleted( self.get_snapshot, kwargs.get("snapshot_id")) diff --git a/contrib/tempest/tempest/share_exceptions.py b/contrib/tempest/tempest/share_exceptions.py index 14b198a585..0f3de8592f 100644 --- a/contrib/tempest/tempest/share_exceptions.py +++ b/contrib/tempest/tempest/share_exceptions.py @@ -20,6 +20,10 @@ class ShareBuildErrorException(exceptions.TempestException): message = "Share %(share_id)s failed to build and is in ERROR status" +class ShareInstanceBuildErrorException(exceptions.TempestException): + message = "Share instance %(id)s failed to build and is in ERROR status" + + class AccessRuleBuildErrorException(exceptions.TempestException): message = "Share's rule with id %(rule_id)s is in ERROR status" diff --git a/etc/manila/policy.json b/etc/manila/policy.json index f2b8a4981e..3370be87e0 100644 --- a/etc/manila/policy.json +++ b/etc/manila/policy.json @@ -26,6 +26,9 @@ "share:delete_share_metadata": "rule:default", "share:update_share_metadata": "rule:default", + "share_instance:index": "rule:admin_api", + "share_instance:show": "rule:admin_api", + "share_extension:quotas:show": "", "share_extension:quotas:update": "rule:admin_api", "share_extension:quotas:delete": "rule:admin_api", @@ -35,6 +38,8 @@ "share_extension:share_admin_actions:reset_status": "rule:admin_api", "share_extension:snapshot_admin_actions:force_delete": "rule:admin_api", "share_extension:snapshot_admin_actions:reset_status": "rule:admin_api", + "share_extension:share_instance_admin_actions:force_delete": "rule:admin_api", + "share_extension:share_instance_admin_actions:reset_status": "rule:admin_api", "share_extension:services": "rule:admin_api", "share_extension:availability_zones": "", diff --git a/manila/api/contrib/admin_actions.py b/manila/api/contrib/admin_actions.py index 8bfa9d2910..a61a7a0f6f 100644 --- a/manila/api/contrib/admin_actions.py +++ b/manila/api/contrib/admin_actions.py @@ -113,6 +113,21 @@ class ShareAdminController(AdminController): return self.share_api.delete(*args, **kwargs) +class ShareInstancesAdminController(AdminController): + """AdminController for Share instances.""" + + collection = 'share_instances' + + def _get(self, *args, **kwargs): + return db.share_instance_get(*args, **kwargs) + + def _update(self, *args, **kwargs): + db.share_instance_update(*args, **kwargs) + + def _delete(self, *args, **kwargs): + return self.share_api.delete_instance(*args, **kwargs) + + class SnapshotAdminController(AdminController): """AdminController for Snapshots.""" @@ -133,11 +148,13 @@ class Admin_actions(extensions.ExtensionDescriptor): name = "AdminActions" alias = "os-admin-actions" - updated = "2012-08-25T00:00:00+00:00" + updated = "2015-08-03T00:00:00+00:00" def get_controller_extensions(self): exts = [] - for class_ in (ShareAdminController, SnapshotAdminController): + controllers = (ShareAdminController, SnapshotAdminController, + ShareInstancesAdminController) + for class_ in controllers: controller = class_() extension = extensions.ControllerExtension( self, class_.collection, controller) diff --git a/manila/api/openstack/api_version_request.py b/manila/api/openstack/api_version_request.py index be19108f95..a98c4b4e40 100644 --- a/manila/api/openstack/api_version_request.py +++ b/manila/api/openstack/api_version_request.py @@ -48,6 +48,7 @@ REST_API_VERSION_HISTORY = """ * 1.1 - Versions API updated to reflect beginning of microversions epoch. * 1.2 - Share create() doesn't ignore availability_zone field of share. * 1.3 - Snapshots become optional feature. + * 1.4 - Share instances admin API """ @@ -55,7 +56,7 @@ REST_API_VERSION_HISTORY = """ # The default api version request is defined to be the # the minimum version of the API supported. _MIN_API_VERSION = "1.0" -_MAX_API_VERSION = "1.3" +_MAX_API_VERSION = "1.4" DEFAULT_API_VERSION = _MIN_API_VERSION diff --git a/manila/api/openstack/rest_api_version_history.rst b/manila/api/openstack/rest_api_version_history.rst index 10a6f34a9c..1ae8552304 100644 --- a/manila/api/openstack/rest_api_version_history.rst +++ b/manila/api/openstack/rest_api_version_history.rst @@ -40,3 +40,7 @@ user documentation. --- Snapshots become optional and share payload now has boolean attr 'snapshot_support'. + +1.4 +--- + Share instances admin API and update of Admin Actions extension. diff --git a/manila/api/v1/router.py b/manila/api/v1/router.py index 84bd4fef44..7d1bef0f61 100644 --- a/manila/api/v1/router.py +++ b/manila/api/v1/router.py @@ -26,6 +26,7 @@ import manila.api.openstack from manila.api.v1 import limits from manila.api.v1 import scheduler_stats from manila.api.v1 import security_service +from manila.api.v1 import share_instances from manila.api.v1 import share_metadata from manila.api.v1 import share_networks from manila.api.v1 import share_servers @@ -59,6 +60,18 @@ class APIRouter(manila.api.openstack.APIRouter): collection={'detail': 'GET'}, member={'action': 'POST'}) + self.resources['share_instances'] = share_instances.create_resource() + mapper.resource("share_instance", "share_instances", + controller=self.resources['share_instances'], + collection={'detail': 'GET'}, + member={'action': 'POST'}) + + mapper.connect("share_instance", + "/{project_id}/shares/{share_id}/instances", + controller=self.resources['share_instances'], + action='get_share_instances', + conditions={"method": ['GET']}) + self.resources['snapshots'] = share_snapshots.create_resource() mapper.resource("snapshot", "snapshots", controller=self.resources['snapshots'], diff --git a/manila/api/v1/share_instances.py b/manila/api/v1/share_instances.py new file mode 100644 index 0000000000..624f65d433 --- /dev/null +++ b/manila/api/v1/share_instances.py @@ -0,0 +1,77 @@ +# Copyright 2015 Mirantis 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 webob import exc + +from manila.api.openstack import wsgi +from manila.api.views import share_instance as instance_view +from manila import db +from manila import exception +from manila import policy +from manila import share + + +class ShareInstancesController(wsgi.Controller): + """The share instances API controller for the OpenStack API.""" + + resource_name = 'share_instance' + _view_builder_class = instance_view.ViewBuilder + + def __init__(self): + self.share_api = share.API() + super(ShareInstancesController, self).__init__() + + def _authorize(self, context, action): + try: + policy.check_policy(context, self.resource_name, action) + except exception.PolicyNotAuthorized: + raise exc.HTTPForbidden() + + @wsgi.Controller.api_version("1.4") + def index(self, req): + context = req.environ['manila.context'] + self._authorize(context, 'index') + + instances = db.share_instances_get_all(context) + return self._view_builder.detail_list(req, instances) + + @wsgi.Controller.api_version("1.4") + def show(self, req, id): + context = req.environ['manila.context'] + self._authorize(context, 'show') + + try: + instance = db.share_instance_get(context, id) + except exception.NotFound: + raise exc.HTTPNotFound() + + return self._view_builder.detail(req, instance) + + @wsgi.Controller.api_version("1.4") + def get_share_instances(self, req, share_id): + context = req.environ['manila.context'] + self._authorize(context, 'index') + + try: + share = self.share_api.get(context, share_id) + except exception.NotFound: + raise exc.HTTPNotFound() + + view = instance_view.ViewBuilder() + return view.detail_list(req, share.instances) + + +def create_resource(): + return wsgi.Resource(ShareInstancesController()) \ No newline at end of file diff --git a/manila/api/views/share_instance.py b/manila/api/views/share_instance.py new file mode 100644 index 0000000000..f23bdd8525 --- /dev/null +++ b/manila/api/views/share_instance.py @@ -0,0 +1,56 @@ +# 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 manila.api import common + + +class ViewBuilder(common.ViewBuilder): + """Model a server API response as a python dictionary.""" + + _collection_name = 'share_instances' + + def detail_list(self, request, instances): + """Detailed view of a list of share instances.""" + return self._list_view(self.detail, request, instances) + + def detail(self, request, share_instance): + """Detailed view of a single share instance.""" + export_locations = [e['path'] for e in share_instance.export_locations] + + instance_dict = { + 'id': share_instance.get('id'), + 'share_id': share_instance.get('share_id'), + 'availability_zone': share_instance.get('availability_zone'), + 'created_at': share_instance.get('created_at'), + 'host': share_instance.get('host'), + 'status': share_instance.get('status'), + 'share_network_id': share_instance.get('share_network_id'), + 'share_server_id': share_instance.get('share_server_id'), + 'export_location': share_instance.get('export_location'), + 'export_locations': export_locations, + } + + return {'share_instance': instance_dict} + + def _list_view(self, func, request, instances): + """Provide a view for a list of share instances.""" + instances_list = [func(request, instance)['share_instance'] + for instance in instances] + instances_links = self._get_collection_links(request, + instances, + self._collection_name) + instances_dict = {self._collection_name: instances_list} + + if instances_links: + instances_dict[self._collection_name] = instances_links + + return instances_dict diff --git a/manila/db/api.py b/manila/db/api.py index 0eeb8663d2..631d775c29 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -303,6 +303,11 @@ def share_instance_update(context, instance_id, values, with_share_data=False): with_share_data=with_share_data) +def share_instances_get_all(context): + """Returns all share instances.""" + return IMPL.share_instances_get_all(context) + + def share_instances_get_all_by_share_server(context, share_server_id): """Returns all share instances with given share_server_id.""" return IMPL.share_instances_get_all_by_share_server(context, diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index ccc24e28dc..a2170d6d25 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -1184,6 +1184,15 @@ def share_instance_get(context, share_instance_id, session=None, return result +@require_admin_context +def share_instances_get_all(context): + session = get_session() + return ( + model_query(context, models.ShareInstance, session=session, + read_deleted="no").all() + ) + + @require_context def share_instance_delete(context, instance_id, session=None): if session is None: diff --git a/manila/tests/api/contrib/test_admin_actions.py b/manila/tests/api/contrib/test_admin_actions.py index eabdfd924f..2ce1988b99 100644 --- a/manila/tests/api/contrib/test_admin_actions.py +++ b/manila/tests/api/contrib/test_admin_actions.py @@ -87,6 +87,14 @@ class AdminActionsTest(test.TestCase): snapshot['id']) return snapshot, req + def _setup_share_instance_data(self, instance=None): + if instance is None: + instance = db_utils.create_share(status=constants.STATUS_AVAILABLE, + size='1').instance + req = webob.Request.blank( + '/v1/fake/share_instances/%s/action' % instance['id']) + return instance, req + def _reset_status(self, ctxt, model, req, db_access_method, valid_code, valid_status=None, body=None): if body is None: @@ -130,6 +138,17 @@ class AdminActionsTest(test.TestCase): self._reset_status(ctxt, snapshot, req, db.share_snapshot_get, valid_code, valid_status) + @ddt.data(*fixture_reset_status_with_different_roles) + @ddt.unpack + def test_share_instances_reset_status_with_different_roles(self, role, + valid_code, + valid_status): + ctxt = self._get_context(role) + instance, req = self._setup_share_instance_data() + + self._reset_status(ctxt, instance, req, db.share_instance_get, + valid_code, valid_status) + @ddt.data(*fixture_invalid_reset_status_body) def test_share_invalid_reset_status_body(self, body): share, req = self._setup_share_data() @@ -146,6 +165,14 @@ class AdminActionsTest(test.TestCase): db.share_snapshot_get, 400, constants.STATUS_AVAILABLE, body) + @ddt.data(*fixture_invalid_reset_status_body) + def test_share_instance_invalid_reset_status_body(self, body): + instance, req = self._setup_share_instance_data() + + self._reset_status(self.admin_context, instance, req, + db.share_instance_get, 400, + constants.STATUS_AVAILABLE, body) + def test_share_reset_status_for_missing(self): fake_share = {'id': 'missing-share-id'} req = webob.Request.blank('/v1/fake/shares/%s/action' % @@ -201,3 +228,19 @@ class AdminActionsTest(test.TestCase): ctxt = self._get_context('admin') self._force_delete(ctxt, snapshot, req, db.share_snapshot_get, 404) + + @ddt.data(*fixture_force_delete_with_different_roles) + @ddt.unpack + def test_instance_force_delete_with_different_roles(self, role, resp_code): + instance, req = self._setup_share_instance_data() + ctxt = self._get_context(role) + + self._force_delete(ctxt, instance, req, db.share_instance_get, + resp_code) + + def test_instance_force_delete_missing(self): + instance, req = self._setup_share_instance_data( + instance={'id': 'fake'}) + ctxt = self._get_context('admin') + + self._force_delete(ctxt, instance, req, db.share_instance_get, 404) diff --git a/manila/tests/api/v1/test_share_instances.py b/manila/tests/api/v1/test_share_instances.py new file mode 100644 index 0000000000..0cdc7b87d3 --- /dev/null +++ b/manila/tests/api/v1/test_share_instances.py @@ -0,0 +1,93 @@ +# 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_config import cfg +from webob import exc as webob_exc + +from manila.api.v1 import share_instances +from manila import context +from manila import test +from manila.tests.api import fakes +from manila.tests import db_utils + +CONF = cfg.CONF + + +@ddt.ddt +class ShareInstancesApiTest(test.TestCase): + """Share Api Test.""" + def setUp(self): + super(ShareInstancesApiTest, self).setUp() + self.controller = share_instances.ShareInstancesController() + self.context = context.RequestContext('admin', 'fake', True) + + def _get_request(self, uri, context=None): + if context is None: + context = self.context + req = fakes.HTTPRequest.blank('/shares', version="1.4") + req.environ['manila.context'] = context + return req + + def _validate_ids_in_share_instances_list(self, expected, actual): + self.assertEqual(len(expected), len(actual)) + self.assertEqual([i['id'] for i in expected], + [i['id'] for i in actual]) + + def test_index(self): + req = self._get_request('/share_instances') + share_instances_count = 3 + test_instances = [ + db_utils.create_share(size=s + 1).instance + for s in range(0, share_instances_count) + ] + + actual_result = self.controller.index(req) + + self._validate_ids_in_share_instances_list( + test_instances, actual_result['share_instances']) + + def test_show(self): + test_instance = db_utils.create_share(size=1).instance + id = test_instance['id'] + + actual_result = self.controller.show(self._get_request('fake'), id) + + self.assertEqual(id, actual_result['share_instance']['id']) + + def test_get_share_instances(self): + test_share = db_utils.create_share(size=1) + id = test_share['id'] + req = self._get_request('fake') + + actual_result = self.controller.get_share_instances(req, id) + + self._validate_ids_in_share_instances_list( + [test_share.instance], + actual_result['share_instances'] + ) + + @ddt.data('show', 'get_share_instances') + def test_not_found(self, target_method_name): + method = getattr(self.controller, target_method_name) + self.assertRaises(webob_exc.HTTPNotFound, method, + self._get_request('fake'), 'fake') + + @ddt.data(('show', 2), ('get_share_instances', 2), ('index', 1)) + @ddt.unpack + def test_access(self, target_method_name, args_count): + user_context = context.RequestContext('fake', 'fake') + req = self._get_request('fake', user_context) + target_method = getattr(self.controller, target_method_name) + args = [i for i in range(1, args_count)] + + self.assertRaises(webob_exc.HTTPForbidden, target_method, req, *args) diff --git a/manila/tests/policy.json b/manila/tests/policy.json index 9a450fbc29..2290702477 100644 --- a/manila/tests/policy.json +++ b/manila/tests/policy.json @@ -18,6 +18,9 @@ "share:extend": "", "share:shrink": "", + "share_instance:index": "rule:admin_api", + "share_instance:show": "rule:admin_api", + "share_network:create": "", "share_network:index": "", "share_network:detail": "", @@ -38,6 +41,8 @@ "share_extension:share_admin_actions:reset_status": "rule:admin_api", "share_extension:snapshot_admin_actions:force_delete": "rule:admin_api", "share_extension:snapshot_admin_actions:reset_status": "rule:admin_api", + "share_extension:share_instance_admin_actions:force_delete": "rule:admin_api", + "share_extension:share_instance_admin_actions:reset_status": "rule:admin_api", "share_extension:types_manage": "", "share_extension:types_extra_specs": "", "share_extension:share_type_access": "",