diff --git a/etc/policy.json b/etc/policy.json index e72363f6d5..325f00b21f 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -41,6 +41,7 @@ "add_metadef_object":"", "list_metadef_resource_types":"", + "get_metadef_resource_type":"", "add_metadef_resource_type_association":"", "get_metadef_property":"", diff --git a/glance/api/authorization.py b/glance/api/authorization.py index beef833dbf..c5eb4eb7bd 100644 --- a/glance/api/authorization.py +++ b/glance/api/authorization.py @@ -723,6 +723,10 @@ class MetadefResourceTypeRepoProxy( return [proxy_meta_resource_type(self.context, meta_resource_type) for meta_resource_type in meta_resource_types] + def get(self, *args, **kwargs): + meta_resource_type = self.meta_resource_type_repo.get(*args, **kwargs) + return proxy_meta_resource_type(self.context, meta_resource_type) + # Metadef namespace properties classes def is_namespace_property_mutable(context, namespace_property): diff --git a/glance/api/policy.py b/glance/api/policy.py index 98f5cc44e7..c2df6c1aa7 100644 --- a/glance/api/policy.py +++ b/glance/api/policy.py @@ -580,6 +580,10 @@ class MetadefResourceTypeRepoProxy( self.policy.enforce(self.context, 'list_metadef_resource_types', {}) return super(MetadefResourceTypeRepoProxy, self).list(*args, **kwargs) + def get(self, *args, **kwargs): + self.policy.enforce(self.context, 'get_metadef_resource_type', {}) + return super(MetadefResourceTypeRepoProxy, self).get(*args, **kwargs) + def add(self, resource_type): self.policy.enforce(self.context, 'add_metadef_resource_type_association', {}) diff --git a/glance/api/v2/metadef_properties.py b/glance/api/v2/metadef_properties.py index 5f2760695e..8bf79e3728 100644 --- a/glance/api/v2/metadef_properties.py +++ b/glance/api/v2/metadef_properties.py @@ -79,8 +79,24 @@ class NamespacePropertiesController(object): raise webob.exc.HTTPInternalServerError() return namespace_properties - def show(self, req, namespace, property_name): + def show(self, req, namespace, property_name, filters=None): try: + if filters and filters['resource_type']: + rs_repo = self.gateway.get_metadef_resource_type_repo( + req.context) + db_resource_type = rs_repo.get(filters['resource_type'], + namespace) + prefix = db_resource_type.prefix + if prefix and property_name.startswith(prefix): + property_name = property_name[len(prefix):] + else: + msg = (_("Property %(property_name)s does not start " + "with the expected resource type association " + "prefix of '%(prefix)s'.") + % {'property_name': property_name, + 'prefix': prefix}) + raise exception.NotFound(msg) + prop_repo = self.gateway.get_metadef_property_repo(req.context) db_property = prop_repo.get(namespace, property_name) property = self._to_model(db_property) @@ -185,6 +201,13 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer): property_type = fromjson(PropertyType, body) return dict(property_type=property_type) + def show(self, request): + params = request.params.copy() + query_params = { + 'filters': params + } + return query_params + class ResponseSerializer(wsgi.JSONResponseSerializer): def __init__(self, schema=None): diff --git a/glance/db/__init__.py b/glance/db/__init__.py index f6e402b5ce..08b51785d3 100644 --- a/glance/db/__init__.py +++ b/glance/db/__init__.py @@ -628,6 +628,19 @@ class MetadefResourceTypeRepo(object): self._format_resource_type_to_db(resource_type) ) + def get(self, resource_type, namespace): + namespace_entity = self.meta_namespace_repo.get(namespace) + db_resource_type = ( + self.db_api. + metadef_resource_type_association_get( + self.context, + namespace, + resource_type + ) + ) + return self._format_resource_type_from_db(db_resource_type, + namespace_entity) + def list(self, filters=None): namespace = filters['namespace'] if namespace: diff --git a/glance/domain/proxy.py b/glance/domain/proxy.py index 3cdf35dc4f..dd8bdea32b 100644 --- a/glance/domain/proxy.py +++ b/glance/domain/proxy.py @@ -367,6 +367,10 @@ class MetadefResourceTypeRepo(object): self.base.add(self.resource_type_proxy_helper.unproxy( meta_resource_type)) + def get(self, *args, **kwargs): + resource_type = self.base.get(*args, **kwargs) + return self.resource_type_proxy_helper.proxy(resource_type) + def list(self, *args, **kwargs): resource_types = self.base.list(*args, **kwargs) return [self.resource_type_proxy_helper.proxy(resource_type) diff --git a/glance/tests/functional/v2/test_metadef_properties.py b/glance/tests/functional/v2/test_metadef_properties.py index 7983cf2532..b5e791b69a 100644 --- a/glance/tests/functional/v2/test_metadef_properties.py +++ b/glance/tests/functional/v2/test_metadef_properties.py @@ -55,15 +55,22 @@ class TestNamespaceProperties(functional.FunctionalTest): path = self._url('/v2/metadefs/namespaces') headers = self._headers({'content-type': 'application/json'}) namespace_name = 'MyNamespace' + resource_type_name = 'MyResourceType' + resource_type_prefix = 'MyPrefix' data = jsonutils.dumps({ "namespace": namespace_name, "display_name": "My User Friendly Namespace", "description": "My description", "visibility": "public", "protected": False, - "owner": "The Test Owner" - } - ) + "owner": "The Test Owner", + "resource_type_associations": [ + { + "name": resource_type_name, + "prefix": resource_type_prefix + } + ] + }) response = requests.post(path, headers=headers, data=data) self.assertEqual(201, response.status_code) @@ -105,6 +112,30 @@ class TestNamespaceProperties(functional.FunctionalTest): self.assertEqual(100, property_object['minimum']) self.assertEqual(30000369, property_object['maximum']) + # Get the property with specific resource type association + path = self._url('/v2/metadefs/namespaces/%s/properties/%s%s' % ( + namespace_name, property_name, '='.join(['?resource_type', + resource_type_name]))) + response = requests.get(path, headers=self._headers()) + self.assertEqual(404, response.status_code) + + # Get the property with prefix and specific resource type association + property_name_with_prefix = ''.join([resource_type_prefix, + property_name]) + path = self._url('/v2/metadefs/namespaces/%s/properties/%s%s' % ( + namespace_name, property_name_with_prefix, '='.join([ + '?resource_type', resource_type_name]))) + response = requests.get(path, headers=self._headers()) + self.assertEqual(200, response.status_code) + property_object = jsonutils.loads(response.text) + self.assertEqual("integer", property_object['type']) + self.assertEqual("property1", property_object['title']) + self.assertEqual("property1 description", property_object[ + 'description']) + self.assertEqual('100', property_object['default']) + self.assertEqual(100, property_object['minimum']) + self.assertEqual(30000369, property_object['maximum']) + # Returned property should match the created property property_object = jsonutils.loads(response.text) checked_keys = set([ diff --git a/glance/tests/unit/v2/test_metadef_resources.py b/glance/tests/unit/v2/test_metadef_resources.py index 9bd1599e50..2d82f22a34 100644 --- a/glance/tests/unit/v2/test_metadef_resources.py +++ b/glance/tests/unit/v2/test_metadef_resources.py @@ -38,6 +38,7 @@ NAMESPACE6 = 'Namespace6' PROPERTY1 = 'Property1' PROPERTY2 = 'Property2' PROPERTY3 = 'Property3' +PROPERTY4 = 'Property4' OBJECT1 = 'Object1' OBJECT2 = 'Object2' @@ -46,12 +47,15 @@ OBJECT3 = 'Object3' RESOURCE_TYPE1 = 'ResourceType1' RESOURCE_TYPE2 = 'ResourceType2' RESOURCE_TYPE3 = 'ResourceType3' +RESOURCE_TYPE4 = 'ResourceType4' TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df' TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81' TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8' TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4' +PREFIX1 = 'pref' + def _db_namespace_fixture(namespace, **kwargs): obj = { @@ -146,6 +150,7 @@ class TestMetadefsControllers(base.IsolatedUnitTest): (NAMESPACE3, _db_property_fixture(PROPERTY1)), (NAMESPACE3, _db_property_fixture(PROPERTY2)), (NAMESPACE1, _db_property_fixture(PROPERTY1)), + (NAMESPACE6, _db_property_fixture(PROPERTY4)), ] [self.db.metadef_property_create(req.context, namespace, property) for namespace, property in self.properties] @@ -165,6 +170,7 @@ class TestMetadefsControllers(base.IsolatedUnitTest): self.resource_types = [ _db_resource_type_fixture(RESOURCE_TYPE1), _db_resource_type_fixture(RESOURCE_TYPE2), + _db_resource_type_fixture(RESOURCE_TYPE4), ] [self.db.metadef_resource_type_create(req.context, resource_type) for resource_type in self.resource_types] @@ -176,6 +182,8 @@ class TestMetadefsControllers(base.IsolatedUnitTest): (NAMESPACE3, _db_namespace_resource_type_fixture(RESOURCE_TYPE1)), (NAMESPACE2, _db_namespace_resource_type_fixture(RESOURCE_TYPE1)), (NAMESPACE2, _db_namespace_resource_type_fixture(RESOURCE_TYPE2)), + (NAMESPACE6, _db_namespace_resource_type_fixture(RESOURCE_TYPE4, + prefix=PREFIX1)), ] [self.db.metadef_resource_type_association_create(req.context, namespace, @@ -526,6 +534,25 @@ class TestMetadefsControllers(base.IsolatedUnitTest): output = self.property_controller.show(request, NAMESPACE3, PROPERTY1) self.assertEqual(output.name, PROPERTY1) + def test_property_show_specific_resource_type(self): + request = unit_test_utils.get_fake_request() + output = self.property_controller.show( + request, NAMESPACE6, ''.join([PREFIX1, PROPERTY4]), + filters={'resource_type': RESOURCE_TYPE4}) + self.assertEqual(output.name, PROPERTY4) + + def test_property_show_prefix_mismatch(self): + request = unit_test_utils.get_fake_request() + self.assertRaises(webob.exc.HTTPNotFound, + self.property_controller.show, request, NAMESPACE6, + PROPERTY4, filters={'resource_type': RESOURCE_TYPE4}) + + def test_property_show_non_existing_resource_type(self): + request = unit_test_utils.get_fake_request() + self.assertRaises(webob.exc.HTTPNotFound, + self.property_controller.show, request, NAMESPACE2, + PROPERTY1, filters={'resource_type': 'test'}) + def test_property_show_non_existing(self): request = unit_test_utils.get_fake_request() self.assertRaises(webob.exc.HTTPNotFound, @@ -958,10 +985,10 @@ class TestMetadefsControllers(base.IsolatedUnitTest): request = unit_test_utils.get_fake_request() output = self.rt_controller.index(request) - self.assertEqual(2, len(output.resource_types)) + self.assertEqual(3, len(output.resource_types)) actual = set([type.name for type in output.resource_types]) - expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2]) + expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2, RESOURCE_TYPE4]) self.assertEqual(actual, expected) def test_resource_type_show(self):