summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/api/openstack/__init__.py10
-rw-r--r--nova/api/openstack/extensions.py151
-rw-r--r--nova/api/openstack/incubator/__init__.py20
-rw-r--r--nova/api/openstack/incubator/volumes/__init__.py18
-rw-r--r--nova/api/openstack/incubator/volumes/volume_attachments.py (renamed from nova/api/openstack/volume_attachments.py)0
-rw-r--r--nova/api/openstack/incubator/volumes/volumes.py (renamed from nova/api/openstack/volumes.py)0
-rw-r--r--nova/api/openstack/incubator/volumes/volumes_ext.py55
-rw-r--r--nova/tests/api/openstack/extensions/foxinsocks/__init__.py19
-rw-r--r--nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py (renamed from nova/tests/api/openstack/extensions/foxinsocks.py)2
9 files changed, 244 insertions, 31 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 0e5b2a071..731e16a58 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -128,16 +128,6 @@ class APIRouter(wsgi.Router):
_limits = limits.LimitsController()
mapper.resource("limit", "limits", controller=_limits)
- #NOTE(justinsb): volumes is not yet part of the official API
- mapper.resource("volume", "volumes",
- controller=volumes.Controller(),
- collection={'detail': 'GET'})
-
- mapper.resource("volume_attachment", "volume_attachments",
- controller=volume_attachments.Controller(),
- parent_resource=dict(member_name='server',
- collection_name='servers'))
-
super(APIRouter, self).__init__(mapper)
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 9d98d849a..6a8ce9669 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
+# Copyright 2011 Justin Santa Barbara
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -16,12 +17,14 @@
# under the License.
import imp
+import inspect
import os
import sys
import routes
import webob.dec
import webob.exc
+from nova import exception
from nova import flags
from nova import log as logging
from nova import wsgi
@@ -34,6 +37,63 @@ LOG = logging.getLogger('extensions')
FLAGS = flags.FLAGS
+class ExtensionDescriptor(object):
+ """This is the base class that defines the contract for extensions"""
+
+ def get_name(self):
+ """The name of the extension
+
+ e.g. 'Fox In Socks' """
+ raise NotImplementedError()
+
+ def get_alias(self):
+ """The alias for the extension
+
+ e.g. 'FOXNSOX'"""
+ raise NotImplementedError()
+
+ def get_description(self):
+ """Friendly description for the extension
+
+ e.g. 'The Fox In Socks Extension'"""
+ raise NotImplementedError()
+
+ def get_namespace(self):
+ """The XML namespace for the extension
+
+ e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'"""
+ raise NotImplementedError()
+
+ def get_updated(self):
+ """The timestamp when the extension was last updated
+
+ e.g. '2011-01-22T13:25:27-06:00'"""
+ #NOTE(justinsb): Huh? Isn't this defined by the namespace?
+ raise NotImplementedError()
+
+ def get_resources(self):
+ """List of extensions.ResourceExtension extension objects
+
+ Resources define new nouns, and are accessible through URLs"""
+ resources = []
+ return resources
+
+ def get_actions(self):
+ """List of extensions.ActionExtension extension objects
+
+ Actions are verbs callable from the API"""
+ actions = []
+ return actions
+
+ def get_response_extensions(self):
+ """List of extensions.ResponseExtension extension objects
+
+ Response extensions are used to insert information into existing
+ response data"""
+ response_exts = []
+ return response_exts
+
+
class ActionExtensionController(wsgi.Controller):
def __init__(self, application):
@@ -109,13 +169,10 @@ class ExtensionController(wsgi.Controller):
return self._translate(ext)
def delete(self, req, id):
- raise faults.Fault(exc.HTTPNotFound())
+ raise faults.Fault(webob.exc.HTTPNotFound())
def create(self, req):
- raise faults.Fault(exc.HTTPNotFound())
-
- def delete(self, req, id):
- raise faults.Fault(exc.HTTPNotFound())
+ raise faults.Fault(webob.exc.HTTPNotFound())
class ExtensionMiddleware(wsgi.Middleware):
@@ -235,16 +292,19 @@ class ExtensionMiddleware(wsgi.Middleware):
class ExtensionManager(object):
"""
Load extensions from the configured extension path.
- See nova/tests/api/openstack/extensions/foxinsocks.py for an example
- extension implementation.
+
+ See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an
+ example extension implementation.
"""
def __init__(self, path):
LOG.audit(_('Initializing extension manager.'))
+ self.super_verbose = False
+
self.path = path
self.extensions = {}
- self._load_extensions()
+ self._load_all_extensions()
def get_resources(self):
"""
@@ -300,7 +360,7 @@ class ExtensionManager(object):
except AttributeError as ex:
LOG.exception(_("Exception loading extension: %s"), unicode(ex))
- def _load_extensions(self):
+ def _load_all_extensions(self):
"""
Load extensions from the configured path. The extension name is
constructed from the module_name. If your extension module was named
@@ -310,23 +370,74 @@ class ExtensionManager(object):
See nova/tests/api/openstack/extensions/foxinsocks.py for an example
extension implementation.
"""
- if not os.path.exists(self.path):
+ self._load_extensions_under_path(self.path)
+
+ incubator_path = os.path.join(os.path.dirname(__file__), "incubator")
+ self._load_extensions_under_path(incubator_path)
+
+ def _load_extensions_under_path(self, path):
+ if not os.path.isdir(path):
+ LOG.warning(_('Extensions directory not found: %s') % path)
return
- for f in os.listdir(self.path):
- LOG.audit(_('Loading extension file: %s'), f)
+ LOG.debug(_('Looking for extensions in: %s') % path)
+
+ for child in os.listdir(path):
+ child_path = os.path.join(path, child)
+ if not os.path.isdir(child_path):
+ continue
+ self._load_extension(child_path)
+
+ def _load_extension(self, path):
+ if not os.path.isdir(path):
+ return
+
+ for f in os.listdir(path):
mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
- ext_path = os.path.join(self.path, f)
- if file_ext.lower() == '.py':
- mod = imp.load_source(mod_name, ext_path)
- ext_name = mod_name[0].upper() + mod_name[1:]
+ if file_ext.startswith('_'):
+ continue
+ if file_ext.lower() != '.py':
+ continue
+
+ ext_path = os.path.join(path, f)
+ if self.super_verbose:
+ LOG.debug(_('Checking extension file: %s'), ext_path)
+
+ mod = imp.load_source(mod_name, ext_path)
+ for _name, cls in inspect.getmembers(mod):
try:
- new_ext = getattr(mod, ext_name)()
- self._check_extension(new_ext)
- self.extensions[new_ext.get_alias()] = new_ext
+ if not inspect.isclass(cls):
+ continue
+
+ #NOTE(justinsb): It seems that python modules aren't great
+ # If you have two identically named modules, the classes
+ # from both are mixed in. So name your extension based
+ # on the alias, not 'extension.py'!
+ #TODO(justinsb): Any way to work around this?
+
+ if self.super_verbose:
+ LOG.debug(_('Checking class: %s'), cls)
+
+ if not ExtensionDescriptor in cls.__bases__:
+ if self.super_verbose:
+ LOG.debug(_('Not a ExtensionDescriptor: %s'), cls)
+ continue
+
+ obj = cls()
+ self._add_extension(obj)
except AttributeError as ex:
LOG.exception(_("Exception loading extension: %s"),
- unicode(ex))
+ unicode(ex))
+
+ def _add_extension(self, ext):
+ alias = ext.get_alias()
+ LOG.audit(_('Loaded extension: %s'), alias)
+
+ self._check_extension(ext)
+
+ if alias in self.extensions:
+ raise exception.Error("Found duplicate extension: %s" % alias)
+ self.extensions[alias] = ext
class ResponseExtension(object):
diff --git a/nova/api/openstack/incubator/__init__.py b/nova/api/openstack/incubator/__init__.py
new file mode 100644
index 000000000..cded38174
--- /dev/null
+++ b/nova/api/openstack/incubator/__init__.py
@@ -0,0 +1,20 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Justin Santa Barbara
+# 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 datetime
+
+"""Incubator contains extensions that are shipped with nova.
+
+It can't be called 'extensions' because that causes namespacing problems."""
diff --git a/nova/api/openstack/incubator/volumes/__init__.py b/nova/api/openstack/incubator/volumes/__init__.py
new file mode 100644
index 000000000..2a9c93210
--- /dev/null
+++ b/nova/api/openstack/incubator/volumes/__init__.py
@@ -0,0 +1,18 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Justin Santa Barbara
+# 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 datetime
+
+"""The volumes extension adds volumes and attachments to the API."""
diff --git a/nova/api/openstack/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py
index 58a9a727b..58a9a727b 100644
--- a/nova/api/openstack/volume_attachments.py
+++ b/nova/api/openstack/incubator/volumes/volume_attachments.py
diff --git a/nova/api/openstack/volumes.py b/nova/api/openstack/incubator/volumes/volumes.py
index ec3b9a6c8..ec3b9a6c8 100644
--- a/nova/api/openstack/volumes.py
+++ b/nova/api/openstack/incubator/volumes/volumes.py
diff --git a/nova/api/openstack/incubator/volumes/volumes_ext.py b/nova/api/openstack/incubator/volumes/volumes_ext.py
new file mode 100644
index 000000000..87a57320d
--- /dev/null
+++ b/nova/api/openstack/incubator/volumes/volumes_ext.py
@@ -0,0 +1,55 @@
+# Copyright 2011 Justin Santa Barbara
+# 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 nova.api.openstack import extensions
+from nova.api.openstack.incubator.volumes import volumes
+from nova.api.openstack.incubator.volumes import volume_attachments
+
+
+class VolumesExtension(extensions.ExtensionDescriptor):
+ def get_name(self):
+ return "Volumes"
+
+ def get_alias(self):
+ return "VOLUMES"
+
+ def get_description(self):
+ return "Volumes support"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/volumes/api/v1.1"
+
+ def get_updated(self):
+ return "2011-03-25T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = []
+
+ #NOTE(justinsb): No way to provide singular name ('volume')
+ # Does this matter?
+ res = extensions.ResourceExtension('volumes',
+ volumes.Controller(),
+ collection_actions={'detail': 'GET'}
+ )
+ resources.append(res)
+
+ res = extensions.ResourceExtension('volume_attachments',
+ volume_attachments.Controller(),
+ parent=dict(
+ member_name='server',
+ collection_name='servers'))
+ resources.append(res)
+
+ return resources
diff --git a/nova/tests/api/openstack/extensions/foxinsocks/__init__.py b/nova/tests/api/openstack/extensions/foxinsocks/__init__.py
new file mode 100644
index 000000000..fe505359d
--- /dev/null
+++ b/nova/tests/api/openstack/extensions/foxinsocks/__init__.py
@@ -0,0 +1,19 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# Copyright 2011 Justin Santa Barbara
+# 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 datetime
+
+"""Example Fox-in-Socks extension."""
diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py
index 0860b51ac..442707714 100644
--- a/nova/tests/api/openstack/extensions/foxinsocks.py
+++ b/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py
@@ -28,7 +28,7 @@ class FoxInSocksController(wsgi.Controller):
return "Try to say this Mr. Knox, sir..."
-class Foxinsocks(object):
+class Foxinsocks(extensions.ExtensionDescriptor):
def __init__(self):
pass