Skip to content

Commit

Permalink
Implements data-driven views and extended attributes.
Browse files Browse the repository at this point in the history
The quantum/api/v2/views.py module is replaced by is_visible
properties in the RESOURCE_ATTRIBUTE_MAP defined in
quantum/api/v2/attributes.py. Extensions are given the ability to add
extended attribute descriptions to this map during initialization,
allowing extended attributes to be implemented similarly to core
attributes in plugins.

Resolves bug 1023111.

Change-Id: Ic6e224d5d841b6a1d4d1c762d7306adaf91f7a2d
  • Loading branch information
Bob Kukura committed Jul 23, 2012
1 parent 73ec08b commit a01ca27
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 85 deletions.
81 changes: 56 additions & 25 deletions quantum/api/v2/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,56 +141,87 @@ def convert_to_boolean(data):
RESOURCE_ATTRIBUTE_MAP = {
'networks': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:regex': UUID_PATTERN}},
'name': {'allow_post': True, 'allow_put': True},
'subnets': {'allow_post': True, 'allow_put': True, 'default': []},
'validate': {'type:regex': UUID_PATTERN},
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'is_visible': True},
'subnets': {'allow_post': True, 'allow_put': True,
'default': [],
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True, 'convert_to': convert_to_boolean,
'validate': {'type:boolean': None}},
'status': {'allow_post': False, 'allow_put': False},
'default': True,
'convert_to': convert_to_boolean,
'validate': {'type:boolean': None},
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True},
'required_by_policy': True,
'is_visible': True},
'mac_ranges': {'allow_post': False, 'allow_put': False,
'is_visible': True},
},
'ports': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:regex': UUID_PATTERN}},
'validate': {'type:regex': UUID_PATTERN},
'is_visible': True},
'network_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:regex': UUID_PATTERN}},
'validate': {'type:regex': UUID_PATTERN},
'is_visible': True},
'admin_state_up': {'allow_post': True, 'allow_put': True,
'default': True, 'convert_to': convert_to_boolean,
'validate': {'type:boolean': None}},
'default': True,
'convert_to': convert_to_boolean,
'validate': {'type:boolean': None},
'is_visible': True},
'mac_address': {'allow_post': True, 'allow_put': False,
'default': ATTR_NOT_SPECIFIED,
'validate': {'type:mac_address': None}},
'validate': {'type:mac_address': None},
'is_visible': True},
'fixed_ips': {'allow_post': True, 'allow_put': True,
'default': ATTR_NOT_SPECIFIED},
'default': ATTR_NOT_SPECIFIED,
'is_visible': True},
'host_routes': {'allow_post': True, 'allow_put': True,
'default': ATTR_NOT_SPECIFIED},
'device_id': {'allow_post': True, 'allow_put': True, 'default': ''},
'default': ATTR_NOT_SPECIFIED,
'is_visible': False},
'device_id': {'allow_post': True, 'allow_put': True,
'default': '',
'is_visible': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True},
'required_by_policy': True,
'is_visible': True},
'status': {'allow_post': False, 'allow_put': False,
'is_visible': True},
},
'subnets': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:regex': UUID_PATTERN}},
'validate': {'type:regex': UUID_PATTERN},
'is_visible': True},
'ip_version': {'allow_post': True, 'allow_put': False,
'convert_to': int,
'validate': {'type:values': [4, 6]}},
'validate': {'type:values': [4, 6]},
'is_visible': True},
'network_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:regex': UUID_PATTERN}},
'validate': {'type:regex': UUID_PATTERN},
'is_visible': True},
'cidr': {'allow_post': True, 'allow_put': False,
'validate': {'type:subnet': None}},
'validate': {'type:subnet': None},
'is_visible': True},
'gateway_ip': {'allow_post': True, 'allow_put': True,
'default': ATTR_NOT_SPECIFIED,
'validate': {'type:ip_address': None}},
'validate': {'type:ip_address': None},
'is_visible': True},
#TODO(salvatore-orlando): Enable PUT on allocation_pools
'allocation_pools': {'allow_post': True, 'allow_put': False,
'default': ATTR_NOT_SPECIFIED},
'default': ATTR_NOT_SPECIFIED,
'is_visible': True},
'dns_namesevers': {'allow_post': True, 'allow_put': True,
'default': ATTR_NOT_SPECIFIED},
'default': ATTR_NOT_SPECIFIED,
'is_visible': False},
'additional_host_routes': {'allow_post': True, 'allow_put': True,
'default': ATTR_NOT_SPECIFIED},
'default': ATTR_NOT_SPECIFIED,
'is_visible': False},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True},
'required_by_policy': True,
'is_visible': True},
}
}
14 changes: 12 additions & 2 deletions quantum/api/v2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

from quantum.api.v2 import attributes
from quantum.api.v2 import resource as wsgi_resource
from quantum.api.v2 import views
from quantum.common import exceptions
from quantum.common import utils
from quantum import policy
Expand Down Expand Up @@ -112,7 +111,18 @@ def __init__(self, plugin, collection, resource, attr_info):
self._policy_attrs = [name for (name, info) in self._attr_info.items()
if 'required_by_policy' in info
and info['required_by_policy']]
self._view = getattr(views, self._resource)

def _is_visible(self, attr):
attr_val = self._attr_info.get(attr)
return attr_val and attr_val['is_visible']

def _view(self, data, fields_to_strip=None):
# make sure fields_to_strip is iterable
if not fields_to_strip:
fields_to_strip = []
return dict(item for item in data.iteritems()
if self._is_visible(item[0])
and not item[0] in fields_to_strip)

def _do_field_list(self, original_fields):
fields_to_add = None
Expand Down
4 changes: 4 additions & 0 deletions quantum/api/v2/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

from quantum.api.v2 import attributes
from quantum.api.v2 import base
from quantum.extensions import extensions
from quantum import manager
from quantum.openstack.common import cfg
from quantum import wsgi
Expand Down Expand Up @@ -69,6 +70,9 @@ def __init__(self, **local_config):
mapper = routes_mapper.Mapper()
plugin = manager.QuantumManager.get_plugin()

ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)

col_kwargs = dict(collection_actions=COLLECTION_ACTIONS,
member_actions=MEMBER_ACTIONS)

Expand Down
44 changes: 0 additions & 44 deletions quantum/api/v2/views.py

This file was deleted.

39 changes: 36 additions & 3 deletions quantum/extensions/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ def get_request_extensions(self):
request_exts = []
return request_exts

def get_extended_attributes(self, version):
"""Map describing extended attributes for core resources.
Extended attributes are implemented by a core plugin similarly
to the attributes defined in the core, and can appear in
request and response messages. Their names are scoped with the
extension's prefix. The core API version is passed to this
function, which must return a
map[<resource_name>][<attribute_name>][<attribute_property>]
specifying the extended resource attribute properties required
by that API version.
"""
return {}

def get_plugin_interface(self):
"""
Returns an abstract class which defines contract for the plugin.
Expand Down Expand Up @@ -340,9 +354,7 @@ def _dispatch(req):
def plugin_aware_extension_middleware_factory(global_config, **local_config):
"""Paste factory."""
def _factory(app):
extensions_path = get_extensions_path()
ext_mgr = PluginAwareExtensionManager(extensions_path,
QuantumManager.get_plugin())
ext_mgr = PluginAwareExtensionManager.get_instance()
return ExtensionMiddleware(app, ext_mgr=ext_mgr)
return _factory

Expand Down Expand Up @@ -398,6 +410,18 @@ def get_request_extensions(self):
pass
return request_exts

def extend_resources(self, version, attr_map):
"""Extend resources with additional attributes."""
for ext in self.extensions.itervalues():
try:
extended_attrs = ext.get_extended_attributes(version)
for resource, resource_attrs in extended_attrs.iteritems():
attr_map[resource].update(resource_attrs)
except AttributeError:
# Extensions aren't required to have extended
# attributes
pass

def _check_extension(self, extension):
"""Checks for required methods in extension objects."""
try:
Expand Down Expand Up @@ -467,6 +491,8 @@ def add_extension(self, ext):

class PluginAwareExtensionManager(ExtensionManager):

_instance = None

def __init__(self, path, plugin):
self.plugin = plugin
super(PluginAwareExtensionManager, self).__init__(path)
Expand Down Expand Up @@ -502,6 +528,13 @@ def _plugin_implements_interface(self, extension):
extension.get_alias()))
return plugin_has_interface

@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls(get_extensions_path(),
QuantumManager.get_plugin())
return cls._instance


class RequestExtension(object):
"""Extend requests and responses of core Quantum OpenStack API controllers.
Expand Down
48 changes: 48 additions & 0 deletions quantum/tests/unit/extensions/v2attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) 2012 OpenStack, LLC.
#
# 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.

EXTENDED_ATTRIBUTES_2_0 = {
'networks': {
'v2attrs:something': {'allow_post': False,
'allow_put': False,
'is_visible': True},
'v2attrs:something_else': {'allow_post': True,
'allow_put': False,
'is_visible': False},
}
}


class V2attributes(object):
def get_name(self):
return "V2 Extended Attributes Example"

def get_alias(self):
return "v2attrs"

def get_description(self):
return "Demonstrates extended attributes on V2 core resources"

def get_namespace(self):
return "http://docs.openstack.org/ext/examples/v2attributes/api/v1.0"

def get_updated(self):
return "2012-07-18T10:00:00-00:00"

def get_extended_attributes(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}

0 comments on commit a01ca27

Please sign in to comment.