diff --git a/etc/manila/policy.json b/etc/manila/policy.json index 9e2cf3332d..7ac91b53ee 100644 --- a/etc/manila/policy.json +++ b/etc/manila/policy.json @@ -5,6 +5,8 @@ "admin_api": "is_admin:True", + "availability_zone:index": "rule:default", + "share:create": "", "share:delete": "rule:default", "share:get": "rule:default", diff --git a/manila/api/openstack/wsgi.py b/manila/api/openstack/wsgi.py index d21961b91b..e007b64a0f 100644 --- a/manila/api/openstack/wsgi.py +++ b/manila/api/openstack/wsgi.py @@ -31,6 +31,7 @@ from manila import exception from manila.i18n import _ from manila.i18n import _LE from manila.i18n import _LI +from manila import policy from manila import wsgi LOG = log.getLogger(__name__) @@ -1110,6 +1111,12 @@ class Controller(object): return decorator + def authorize(self, context, action): + try: + policy.check_policy(context, self.resource_name, action) + except exception.PolicyNotAuthorized: + raise webob.exc.HTTPForbidden() + @staticmethod def is_valid_body(body, entity_name): if not (body and entity_name in body): diff --git a/manila/api/contrib/availability_zones.py b/manila/api/v1/availability_zones.py similarity index 52% rename from manila/api/contrib/availability_zones.py rename to manila/api/v1/availability_zones.py index d5b5cf348e..bbdc3c8c6c 100644 --- a/manila/api/contrib/availability_zones.py +++ b/manila/api/v1/availability_zones.py @@ -13,31 +13,26 @@ # License for the specific language governing permissions and limitations # under the License. -from manila.api import extensions from manila.api.openstack import wsgi +from manila.api.views import availability_zones as availability_zones_views from manila import db -authorize = extensions.extension_authorizer('share', 'availability_zones') +class AvailabilityZoneController(wsgi.Controller): + """The Availability Zone API controller for the OpenStack API.""" + + resource_name = "availability_zone" + _view_builder_class = availability_zones_views.ViewBuilder -class Controller(wsgi.Controller): def index(self, req): + self.authorize(req.environ['manila.context'], 'index') + return self._index(req) + + def _index(self, req): """Describe all known availability zones.""" - context = req.environ['manila.context'] - authorize(context) - azs = db.availability_zone_get_all(context) - return {'availability_zones': azs} + views = db.availability_zone_get_all(req.environ['manila.context']) + return self._view_builder.detail_list(views) -class Availability_zones(extensions.ExtensionDescriptor): - """Describe Availability Zones.""" - - name = 'AvailabilityZones' - alias = 'os-availability-zone' - updated = '2015-07-28T00:00:00+00:00' - - def get_resources(self): - controller = Controller() - res = extensions.ResourceExtension(Availability_zones.alias, - controller) - return [res] +def create_resource(): + return wsgi.Resource(AvailabilityZoneController()) diff --git a/manila/api/v1/router.py b/manila/api/v1/router.py index 437ea1409a..3712f430e5 100644 --- a/manila/api/v1/router.py +++ b/manila/api/v1/router.py @@ -23,6 +23,7 @@ from oslo_log import log from manila.api import extensions import manila.api.openstack +from manila.api.v1 import availability_zones from manila.api.v1 import cgsnapshots from manila.api.v1 import consistency_groups from manila.api.v1 import limits @@ -56,6 +57,14 @@ class APIRouter(manila.api.openstack.APIRouter): mapper.redirect("", "/") + self.resources["availability_zones"] = ( + availability_zones.create_resource()) + mapper.resource("availability-zone", + # TODO(vponomaryov): rename 'os-availability-zone' to + # 'availability-zones' when API urls rename happens. + "os-availability-zone", + controller=self.resources["availability_zones"]) + self.resources['shares'] = shares.create_resource() mapper.resource("share", "shares", controller=self.resources['shares'], diff --git a/manila/api/v1/share_instances.py b/manila/api/v1/share_instances.py index 98704fd8e7..a0447c9734 100644 --- a/manila/api/v1/share_instances.py +++ b/manila/api/v1/share_instances.py @@ -19,7 +19,6 @@ 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 @@ -33,16 +32,10 @@ class ShareInstancesController(wsgi.Controller): 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("2.3") def index(self, req): context = req.environ['manila.context'] - self._authorize(context, 'index') + self.authorize(context, 'index') instances = db.share_instances_get_all(context) return self._view_builder.detail_list(req, instances) @@ -50,7 +43,7 @@ class ShareInstancesController(wsgi.Controller): @wsgi.Controller.api_version("2.3") def show(self, req, id): context = req.environ['manila.context'] - self._authorize(context, 'show') + self.authorize(context, 'show') try: instance = db.share_instance_get(context, id) @@ -62,7 +55,7 @@ class ShareInstancesController(wsgi.Controller): @wsgi.Controller.api_version("2.3") def get_share_instances(self, req, share_id): context = req.environ['manila.context'] - self._authorize(context, 'index') + self.authorize(context, 'index') try: share = self.share_api.get(context, share_id) @@ -74,4 +67,4 @@ class ShareInstancesController(wsgi.Controller): def create_resource(): - return wsgi.Resource(ShareInstancesController()) \ No newline at end of file + return wsgi.Resource(ShareInstancesController()) diff --git a/manila/api/views/availability_zones.py b/manila/api/views/availability_zones.py new file mode 100644 index 0000000000..e35e5dceb3 --- /dev/null +++ b/manila/api/views/availability_zones.py @@ -0,0 +1,31 @@ +# Copyright (c) 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 manila.api import common + + +class ViewBuilder(common.ViewBuilder): + + _collection_name = "availability_zones" + + def _detail(self, availability_zone): + """Detailed view of an single availability zone.""" + keys = ('id', 'name', 'created_at', 'updated_at') + return {key: availability_zone.get(key) for key in keys} + + def detail_list(self, availability_zones): + """Detailed view of a list of availability zones.""" + azs = [self._detail(az) for az in availability_zones] + return {self._collection_name: azs} diff --git a/manila/tests/api/contrib/test_availability_zones.py b/manila/tests/api/contrib/test_availability_zones.py deleted file mode 100644 index d4ad24edf4..0000000000 --- a/manila/tests/api/contrib/test_availability_zones.py +++ /dev/null @@ -1,38 +0,0 @@ -# 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. - -import mock - -from manila.api.contrib import availability_zones -from manila import db -from manila import test -from manila.tests.api import fakes - - -class AvailabilityZonesApiTest(test.TestCase): - def setUp(self): - super(AvailabilityZonesApiTest, self).setUp() - self.controller = availability_zones.Controller() - - def test_index(self): - fake_az = [{'test': 'test'}] - self.mock_object(db, 'availability_zone_get_all', - mock.Mock(return_value=fake_az)) - req = fakes.HTTPRequest.blank('/v2/fake/types/1') - - actual_result = self.controller.index(req) - - self.assertDictMatch({'availability_zones': fake_az}, actual_result) - db.availability_zone_get_all.assert_called_once_with(mock.ANY) diff --git a/manila/tests/api/v1/test_availability_zones.py b/manila/tests/api/v1/test_availability_zones.py new file mode 100644 index 0000000000..ceaea16c86 --- /dev/null +++ b/manila/tests/api/v1/test_availability_zones.py @@ -0,0 +1,71 @@ +# Copyright (c) 2015 Mirantis inc. +# +# 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 manila.api.v1 import availability_zones +from manila import context +from manila import test +from manila.tests.api import fakes + + +@ddt.ddt +class AvailabilityZonesAPITest(test.TestCase): + + def test_instantiate_controller(self): + controller = availability_zones.AvailabilityZoneController() + + self.assertTrue(hasattr(controller, "resource_name")) + self.assertEqual("availability_zone", controller.resource_name) + self.assertTrue(hasattr(controller, "_view_builder")) + self.assertTrue(hasattr(controller._view_builder, "detail_list")) + + @ddt.data('1.0', '2.0', '2.6') + def test_index(self, version): + azs = [ + { + "id": "fake_id1", + "name": "fake_name1", + "created_at": "fake_created_at", + "updated_at": "fake_updated_at", + }, + { + "id": "fake_id2", + "name": "fake_name2", + "created_at": "fake_created_at", + "updated_at": "fake_updated_at", + "deleted": "False", + "redundant_key": "redundant_value", + }, + ] + self.mock_object(availability_zones.db, 'availability_zone_get_all', + mock.Mock(return_value=azs)) + controller = availability_zones.AvailabilityZoneController() + ctxt = context.RequestContext("admin", "fake", True) + req = fakes.HTTPRequest.blank('/shares', version=version) + req.environ['manila.context'] = ctxt + + result = controller.index(req) + + availability_zones.db.availability_zone_get_all.\ + assert_called_once_with(ctxt) + self.assertTrue(isinstance(result, dict)) + self.assertEqual(["availability_zones"], list(result.keys())) + self.assertTrue(isinstance(result["availability_zones"], list)) + self.assertEqual(2, len(result["availability_zones"])) + self.assertTrue(azs[0] in result["availability_zones"]) + azs[1].pop("deleted") + azs[1].pop("redundant_key") + self.assertTrue(azs[1] in result["availability_zones"]) diff --git a/manila/tests/policy.json b/manila/tests/policy.json index 63f5f70fef..9507ec701c 100644 --- a/manila/tests/policy.json +++ b/manila/tests/policy.json @@ -4,6 +4,8 @@ "admin_or_owner": "is_admin:True or project_id:%(project_id)s", "default": "rule:admin_or_owner", + "availability_zone:index": "rule:default", + "share:create": "", "share:list_by_share_server_id": "rule:admin_api", "share:get": "", diff --git a/manila_tempest_tests/tests/api/test_availability_zones.py b/manila_tempest_tests/tests/api/test_availability_zones.py index 7330f41d94..9176683257 100644 --- a/manila_tempest_tests/tests/api/test_availability_zones.py +++ b/manila_tempest_tests/tests/api/test_availability_zones.py @@ -24,10 +24,13 @@ class AvailabilityZonesTest(base.BaseSharesTest): self.assertTrue(len(availability_zones) > 0) keys = ("created_at", "updated_at", "name", "id") for az in availability_zones: + self.assertEqual(len(keys), len(az)) for key in keys: self.assertIn(key, az) @test.attr(type=["smoke", "gate"]) - def test_list_availability_zones_extension(self): + def test_list_availability_zones_extension_url(self): + # NOTE(vponomaryov): remove this test with removal of availability zone + # extension url support. azs = self.shares_client.list_availability_zones() self._list_availability_zones_assertions(azs)