From 6d9e4413b12303c34facf084a002acc01932b95b Mon Sep 17 00:00:00 2001 From: Kaiwei Fan Date: Tue, 5 Feb 2013 13:26:08 -0800 Subject: [PATCH] Add support for extended attributes for extension resources Removed unsed import from quantum/tests/unit/test_extension_extended_attribute.py Address comments. Change update_attributes_map definition/behavior Fixes: bug #1116664 Change-Id: Idc360f5b3b35fb1d40237e1bbce39684508175cf --- quantum/api/extensions.py | 27 ++++ .../unit/extensions/extendedattribute.py | 58 +++++++ .../unit/extensions/extensionattribute.py | 109 ++++++++++++++ .../unit/test_extension_extended_attribute.py | 141 ++++++++++++++++++ 4 files changed, 335 insertions(+) create mode 100644 quantum/tests/unit/extensions/extendedattribute.py create mode 100644 quantum/tests/unit/extensions/extensionattribute.py create mode 100644 quantum/tests/unit/test_extension_extended_attribute.py diff --git a/quantum/api/extensions.py b/quantum/api/extensions.py index d321a86684..3d99111862 100644 --- a/quantum/api/extensions.py +++ b/quantum/api/extensions.py @@ -159,6 +159,26 @@ class ExtensionDescriptor(object): """ return None + def update_attributes_map(self, extended_attributes, + extension_attrs_map=None): + """Update attributes map for this extension + + This is default method for extending an extension's attributes map. + An extension can use this method and supplying its own resource + attribute map in extension_attrs_map argument to extend all its + attributes that needs to be extended. + + If an extension does not implement update_attributes_map, the method + does nothing and just return. + """ + if not extension_attrs_map: + return + + for resource, attrs in extension_attrs_map.iteritems(): + extended_attrs = extended_attributes.get(resource) + if extended_attrs: + attrs.update(extended_attrs) + class ActionExtensionController(wsgi.Controller): @@ -427,9 +447,12 @@ class ExtensionManager(object): After this function, we will extend the attr_map if an extension wants to extend this map. """ + update_exts = [] for ext in self.extensions.itervalues(): if not hasattr(ext, 'get_extended_resources'): continue + if hasattr(ext, 'update_attributes_map'): + update_exts.append(ext) try: extended_attrs = ext.get_extended_resources(version) for resource, resource_attrs in extended_attrs.iteritems(): @@ -443,6 +466,10 @@ class ExtensionManager(object): LOG.exception(_("Error fetching extended attributes for " "extension '%s'"), ext.get_name()) + """Extending extensions' attributes map.""" + for ext in update_exts: + ext.update_attributes_map(attr_map) + def _check_extension(self, extension): """Checks for required methods in extension objects.""" try: diff --git a/quantum/tests/unit/extensions/extendedattribute.py b/quantum/tests/unit/extensions/extendedattribute.py new file mode 100644 index 0000000000..2451b8da4e --- /dev/null +++ b/quantum/tests/unit/extensions/extendedattribute.py @@ -0,0 +1,58 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 VMware, 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. +# +# @author: Kaiwei Fan, VMware, Inc + +from quantum.api import extensions + +EXTENDED_ATTRIBUTE = 'extended_attribute' +EXTENDED_ATTRIBUTES_2_0 = { + 'ext_test_resources': { + EXTENDED_ATTRIBUTE: {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid_or_none': None}, + 'default': None, 'is_visible': True}, + } +} + + +class Extendedattribute(extensions.ExtensionDescriptor): + """Extension class supporting extended attribute for router.""" + + @classmethod + def get_name(cls): + return "Extended Extension Attributes" + + @classmethod + def get_alias(cls): + return "extended-ext-attr" + + @classmethod + def get_description(cls): + return "Provides extended_attr attribute to router" + + @classmethod + def get_namespace(cls): + return "" + + @classmethod + def get_updated(cls): + return "2013-02-05T00:00:00-00:00" + + def get_extended_resources(self, version): + if version == "2.0": + return EXTENDED_ATTRIBUTES_2_0 + else: + return {} diff --git a/quantum/tests/unit/extensions/extensionattribute.py b/quantum/tests/unit/extensions/extensionattribute.py new file mode 100644 index 0000000000..0690538f98 --- /dev/null +++ b/quantum/tests/unit/extensions/extensionattribute.py @@ -0,0 +1,109 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 VMware, 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. +# +# @author: Kaiwei Fan, VMware, Inc +# + +from abc import abstractmethod + +from quantum import manager, quota +from quantum.api import extensions +from quantum.api.v2 import base + + +# Attribute Map +RESOURCE_ATTRIBUTE_MAP = { + 'ext_test_resources': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': None}, + 'is_visible': True, 'default': ''}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': {'type:string': None}, + 'is_visible': True}, + } +} + + +class Extensionattribute(extensions.ExtensionDescriptor): + + @classmethod + def get_name(cls): + return "Extension Test Resource" + + @classmethod + def get_alias(cls): + return "ext-obj-test" + + @classmethod + def get_description(cls): + return "Extension Test Resource" + + @classmethod + def get_namespace(cls): + return "" + + @classmethod + def get_updated(cls): + return "2013-02-05T10:00:00-00:00" + + def update_attributes_map(self, attributes): + super(Extensionattribute, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + + @classmethod + def get_resources(cls): + """ Returns Ext Resources """ + exts = [] + plugin = manager.QuantumManager.get_plugin() + resource_name = 'ext_test_resource' + collection_name = resource_name + "s" + params = RESOURCE_ATTRIBUTE_MAP.get(collection_name, dict()) + + quota.QUOTAS.register_resource_by_name(resource_name) + + controller = base.create_resource(collection_name, + resource_name, + plugin, params, + member_actions={}) + + ex = extensions.ResourceExtension(collection_name, + controller, + member_actions={}) + exts.append(ex) + + return exts + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} + + +class ExtensionObjectTestPluginBase(object): + + @abstractmethod + def create_ext_test_resource(self, context, router): + pass + + @abstractmethod + def get_ext_test_resource(self, context, id, fields=None): + pass diff --git a/quantum/tests/unit/test_extension_extended_attribute.py b/quantum/tests/unit/test_extension_extended_attribute.py new file mode 100644 index 0000000000..62d46ced42 --- /dev/null +++ b/quantum/tests/unit/test_extension_extended_attribute.py @@ -0,0 +1,141 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 VMware, 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. + +""" +Unit tests for extension extended attribute +""" + +import unittest2 as unittest +import webob.exc as webexc + +import quantum +from quantum.api import extensions +from quantum.common import config +from quantum import manager +from quantum.openstack.common import cfg +from quantum.plugins.common import constants +from quantum.plugins.openvswitch import ovs_quantum_plugin +from quantum.tests.unit.extensions import extendedattribute as extattr +from quantum.tests.unit import test_api_v2 +from quantum.tests.unit import testlib_api +from quantum import wsgi + +_uuid = test_api_v2._uuid +_get_path = test_api_v2._get_path +extensions_path = ':'.join(quantum.tests.unit.extensions.__path__) + + +class ExtensionExtendedAttributeTestPlugin( + ovs_quantum_plugin.OVSQuantumPluginV2): + + supported_extension_aliases = [ + 'ext-obj-test', "extended-ext-attr" + ] + + def __init__(self, configfile=None): + super(ExtensionExtendedAttributeTestPlugin, self) + self.objs = [] + self.objh = {} + + def create_ext_test_resource(self, context, ext_test_resource): + obj = ext_test_resource['ext_test_resource'] + id = _uuid() + obj['id'] = id + self.objs.append(obj) + self.objh.update({id: obj}) + return obj + + def get_ext_test_resources(self, context, filters=None, fields=None): + return self.objs + + def get_ext_test_resource(self, context, id, fields=None): + return self.objh[id] + + +class ExtensionExtendedAttributeTestCase(unittest.TestCase): + def setUp(self): + plugin = ( + "quantum.tests.unit.test_extension_extended_attribute." + "ExtensionExtendedAttributeTestPlugin" + ) + + # point config file to: quantum/tests/etc/quantum.conf.test + args = ['--config-file', test_api_v2.etcdir('quantum.conf.test')] + config.parse(args=args) + + cfg.CONF.set_override('core_plugin', plugin) + + manager.QuantumManager._instance = None + + ext_mgr = extensions.PluginAwareExtensionManager( + extensions_path, + {constants.CORE: ExtensionExtendedAttributeTestPlugin} + ) + ext_mgr.extend_resources("2.0", {}) + extensions.PluginAwareExtensionManager._instance = ext_mgr + + app = config.load_paste_app('extensions_test_app') + self._api = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) + + self._tenant_id = "8c70909f-b081-452d-872b-df48e6c355d1" + + def tearDown(self): + # restore original resource attribute map before + self._api = None + cfg.CONF.reset() + + def _do_request(self, method, path, data=None, params=None, action=None): + content_type = 'application/json' + body = None + if data is not None: # empty dict is valid + body = wsgi.Serializer().serialize(data, content_type) + + req = testlib_api.create_request( + path, body, content_type, + method, query_string=params) + res = req.get_response(self._api) + if res.status_code >= 400: + raise webexc.HTTPClientError(detail=res.body, code=res.status_code) + if res.status_code != webexc.HTTPNoContent.code: + return res.json + + def _ext_test_resource_create(self, attr=None): + data = { + "ext_test_resource": { + "tenant_id": self._tenant_id, + "name": "test", + extattr.EXTENDED_ATTRIBUTE: attr + } + } + + res = self._do_request('POST', _get_path('ext_test_resources'), data) + return res['ext_test_resource'] + + def test_ext_test_resource_create(self): + ext_test_resource = self._ext_test_resource_create() + attr = _uuid() + ext_test_resource = self._ext_test_resource_create(attr) + self.assertEqual(ext_test_resource[extattr.EXTENDED_ATTRIBUTE], attr) + + def test_ext_test_resource_get(self): + attr = _uuid() + obj = self._ext_test_resource_create(attr) + obj_id = obj['id'] + res = self._do_request('GET', _get_path( + 'ext_test_resources/{0}'.format(obj_id))) + obj2 = res['ext_test_resource'] + self.assertEqual(obj2[extattr.EXTENDED_ATTRIBUTE], attr)