summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorRick Harris <rick.harris@rackspace.com>2010-12-27 12:13:57 -0600
committerRick Harris <rick.harris@rackspace.com>2010-12-27 12:13:57 -0600
commit54778eacd5e8db448f2079ec82055c3a3aa5d906 (patch)
tree91fa642e89fd158ecf6c14f52a9f98fa834ef087 /nova/api
parenta68f669333c76aeb87ad492541ee3ae290968389 (diff)
parent75e2cbec9eb5132a49446f1b6d563d5f43d007de (diff)
downloadnova-54778eacd5e8db448f2079ec82055c3a3aa5d906.tar.gz
nova-54778eacd5e8db448f2079ec82055c3a3aa5d906.tar.xz
nova-54778eacd5e8db448f2079ec82055c3a3aa5d906.zip
Merging trunk, fixing failed tests
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/__init__.py1
-rw-r--r--nova/api/ec2/__init__.py8
-rw-r--r--nova/api/ec2/cloud.py41
-rw-r--r--nova/api/ec2/metadatarequesthandler.py11
-rw-r--r--nova/api/openstack/__init__.py125
-rw-r--r--nova/api/openstack/auth.py60
-rw-r--r--nova/api/openstack/common.py36
-rw-r--r--nova/api/openstack/flavors.py3
-rw-r--r--nova/api/openstack/images.py6
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py96
-rw-r--r--nova/api/openstack/servers.py3
-rw-r--r--nova/api/openstack/sharedipgroups.py20
12 files changed, 256 insertions, 154 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 803470570..26fed847b 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -24,6 +24,7 @@ Root WSGI middleware for all API controllers.
:ec2api_subdomain: subdomain running the EC2 API (default: ec2)
"""
+import logging
import routes
import webob.dec
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index d1e2596c3..51d33bcc6 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -37,6 +37,9 @@ from nova.auth import manager
FLAGS = flags.FLAGS
+flags.DEFINE_boolean('use_forwarded_for', False,
+ 'Treat X-Forwarded-For as the canonical remote address. '
+ 'Only enable this if you have a sanitizing proxy.')
flags.DEFINE_boolean('use_lockout', False,
'Whether or not to use lockout middleware.')
flags.DEFINE_integer('lockout_attempts', 5,
@@ -144,9 +147,12 @@ class Authenticate(wsgi.Middleware):
raise webob.exc.HTTPForbidden()
# Authenticated!
+ remote_address = req.remote_addr
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
ctxt = context.RequestContext(user=user,
project=project,
- remote_address=req.remote_addr)
+ remote_address=remote_address)
req.environ['ec2.context'] = ctxt
return self.application
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index e1a21f122..e09261f00 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -27,7 +27,6 @@ import datetime
import logging
import re
import os
-import time
from nova import context
import IPy
@@ -699,19 +698,24 @@ class CloudController(object):
context.project_id)
raise quota.QuotaError(_("Address quota exceeded. You cannot "
"allocate any more addresses"))
- network_topic = self._get_network_topic(context)
+ # NOTE(vish): We don't know which network host should get the ip
+ # when we allocate, so just send it to any one. This
+ # will probably need to move into a network supervisor
+ # at some point.
public_ip = rpc.call(context,
- network_topic,
+ FLAGS.network_topic,
{"method": "allocate_floating_ip",
"args": {"project_id": context.project_id}})
return {'addressSet': [{'publicIp': public_ip}]}
def release_address(self, context, public_ip, **kwargs):
- # NOTE(vish): Should we make sure this works?
floating_ip_ref = db.floating_ip_get_by_address(context, public_ip)
- network_topic = self._get_network_topic(context)
+ # NOTE(vish): We don't know which network host should get the ip
+ # when we deallocate, so just send it to any one. This
+ # will probably need to move into a network supervisor
+ # at some point.
rpc.cast(context,
- network_topic,
+ FLAGS.network_topic,
{"method": "deallocate_floating_ip",
"args": {"floating_address": floating_ip_ref['address']}})
return {'releaseResponse': ["Address released."]}
@@ -722,7 +726,10 @@ class CloudController(object):
fixed_address = db.instance_get_fixed_address(context,
instance_ref['id'])
floating_ip_ref = db.floating_ip_get_by_address(context, public_ip)
- network_topic = self._get_network_topic(context)
+ # NOTE(vish): Perhaps we should just pass this on to compute and
+ # let compute communicate with network.
+ network_topic = self.compute_api.get_network_topic(context,
+ internal_id)
rpc.cast(context,
network_topic,
{"method": "associate_floating_ip",
@@ -732,24 +739,18 @@ class CloudController(object):
def disassociate_address(self, context, public_ip, **kwargs):
floating_ip_ref = db.floating_ip_get_by_address(context, public_ip)
- network_topic = self._get_network_topic(context)
+ # NOTE(vish): Get the topic from the host name of the network of
+ # the associated fixed ip.
+ if not floating_ip_ref.get('fixed_ip'):
+ raise exception.ApiError('Address is not associated.')
+ host = floating_ip_ref['fixed_ip']['network']['host']
+ topic = db.queue_get_for(context, FLAGS.network_topic, host)
rpc.cast(context,
- network_topic,
+ topic,
{"method": "disassociate_floating_ip",
"args": {"floating_address": floating_ip_ref['address']}})
return {'disassociateResponse': ["Address disassociated."]}
- def _get_network_topic(self, context):
- """Retrieves the network host for a project"""
- network_ref = self.network_manager.get_network(context)
- host = network_ref['host']
- if not host:
- host = rpc.call(context,
- FLAGS.network_topic,
- {"method": "set_network_host",
- "args": {"network_id": network_ref['id']}})
- return db.queue_get_for(context, FLAGS.network_topic, host)
-
def run_instances(self, context, **kwargs):
max_count = int(kwargs.get('max_count', 1))
instances = self.compute_api.create_instances(context,
diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py
index 0e9e686ff..f832863a9 100644
--- a/nova/api/ec2/metadatarequesthandler.py
+++ b/nova/api/ec2/metadatarequesthandler.py
@@ -23,9 +23,13 @@ import logging
import webob.dec
import webob.exc
+from nova import flags
from nova.api.ec2 import cloud
+FLAGS = flags.FLAGS
+
+
class MetadataRequestHandler(object):
"""Serve metadata from the EC2 API."""
@@ -63,10 +67,13 @@ class MetadataRequestHandler(object):
@webob.dec.wsgify
def __call__(self, req):
cc = cloud.CloudController()
- meta_data = cc.get_metadata(req.remote_addr)
+ remote_address = req.remote_addr
+ if FLAGS.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+ meta_data = cc.get_metadata(remote_address)
if meta_data is None:
logging.error(_('Failed to get metadata for ip: %s') %
- req.remote_addr)
+ remote_address)
raise webob.exc.HTTPNotFound()
data = self.lookup(req.path_info, meta_data)
if data is None:
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 555e5eddd..c49399f28 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -45,10 +45,14 @@ from nova.auth import manager
FLAGS = flags.FLAGS
-flags.DEFINE_string('nova_api_auth',
- 'nova.api.openstack.auth.BasicApiAuthManager',
+flags.DEFINE_string('os_api_auth',
+ 'nova.api.openstack.auth.AuthMiddleware',
'The auth mechanism to use for the OpenStack API implemenation')
+flags.DEFINE_string('os_api_ratelimiting',
+ 'nova.api.openstack.ratelimiting.RateLimitingMiddleware',
+ 'Default ratelimiting implementation for the Openstack API')
+
flags.DEFINE_bool('allow_admin_api',
False,
'When True, this API service will accept admin operations.')
@@ -58,7 +62,10 @@ class API(wsgi.Middleware):
"""WSGI entry point for all OpenStack API requests."""
def __init__(self):
- app = AuthMiddleware(RateLimitingMiddleware(APIRouter()))
+ auth_middleware = utils.import_class(FLAGS.os_api_auth)
+ ratelimiting_middleware = \
+ utils.import_class(FLAGS.os_api_ratelimiting)
+ app = auth_middleware(ratelimiting_middleware(APIRouter()))
super(API, self).__init__(app)
@webob.dec.wsgify
@@ -67,102 +74,11 @@ class API(wsgi.Middleware):
return req.get_response(self.application)
except Exception as ex:
logging.warn(_("Caught error: %s") % str(ex))
- logging.debug(traceback.format_exc())
+ logging.error(traceback.format_exc())
exc = webob.exc.HTTPInternalServerError(explanation=str(ex))
return faults.Fault(exc)
-class AuthMiddleware(wsgi.Middleware):
- """Authorize the openstack API request or return an HTTP Forbidden."""
-
- def __init__(self, application):
- self.auth_driver = utils.import_class(FLAGS.nova_api_auth)()
- super(AuthMiddleware, self).__init__(application)
-
- @webob.dec.wsgify
- def __call__(self, req):
- if 'X-Auth-Token' not in req.headers:
- return self.auth_driver.authenticate(req)
-
- user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"])
-
- if not user:
- return faults.Fault(webob.exc.HTTPUnauthorized())
-
- project = manager.AuthManager().get_project(FLAGS.default_project)
- req.environ['nova.context'] = context.RequestContext(user, project)
- return self.application
-
-
-class RateLimitingMiddleware(wsgi.Middleware):
- """Rate limit incoming requests according to the OpenStack rate limits."""
-
- def __init__(self, application, service_host=None):
- """Create a rate limiting middleware that wraps the given application.
-
- By default, rate counters are stored in memory. If service_host is
- specified, the middleware instead relies on the ratelimiting.WSGIApp
- at the given host+port to keep rate counters.
- """
- super(RateLimitingMiddleware, self).__init__(application)
- if not service_host:
- #TODO(gundlach): These limits were based on limitations of Cloud
- #Servers. We should revisit them in Nova.
- self.limiter = ratelimiting.Limiter(limits={
- 'DELETE': (100, ratelimiting.PER_MINUTE),
- 'PUT': (10, ratelimiting.PER_MINUTE),
- 'POST': (10, ratelimiting.PER_MINUTE),
- 'POST servers': (50, ratelimiting.PER_DAY),
- 'GET changes-since': (3, ratelimiting.PER_MINUTE),
- })
- else:
- self.limiter = ratelimiting.WSGIAppProxy(service_host)
-
- @webob.dec.wsgify
- def __call__(self, req):
- """Rate limit the request.
-
- If the request should be rate limited, return a 413 status with a
- Retry-After header giving the time when the request would succeed.
- """
- action_name = self.get_action_name(req)
- if not action_name:
- # Not rate limited
- return self.application
- delay = self.get_delay(action_name,
- req.environ['nova.context'].user_id)
- if delay:
- # TODO(gundlach): Get the retry-after format correct.
- exc = webob.exc.HTTPRequestEntityTooLarge(
- explanation=_('Too many requests.'),
- headers={'Retry-After': time.time() + delay})
- raise faults.Fault(exc)
- return self.application
-
- def get_delay(self, action_name, username):
- """Return the delay for the given action and username, or None if
- the action would not be rate limited.
- """
- if action_name == 'POST servers':
- # "POST servers" is a POST, so it counts against "POST" too.
- # Attempt the "POST" first, lest we are rate limited by "POST" but
- # use up a precious "POST servers" call.
- delay = self.limiter.perform("POST", username=username)
- if delay:
- return delay
- return self.limiter.perform(action_name, username=username)
-
- def get_action_name(self, req):
- """Return the action name for this request."""
- if req.method == 'GET' and 'changes-since' in req.GET:
- return 'GET changes-since'
- if req.method == 'POST' and req.path_info.startswith('/servers'):
- return 'POST servers'
- if req.method in ['PUT', 'POST', 'DELETE']:
- return req.method
- return None
-
-
class APIRouter(wsgi.Router):
"""
Routes requests on the OpenStack API to the appropriate controller
@@ -195,22 +111,3 @@ class APIRouter(wsgi.Router):
controller=sharedipgroups.Controller())
super(APIRouter, self).__init__(mapper)
-
-
-def limited(items, req):
- """Return a slice of items according to requested offset and limit.
-
- items - a sliceable
- req - wobob.Request possibly containing offset and limit GET variables.
- offset is where to start in the list, and limit is the maximum number
- of items to return.
-
- If limit is not specified, 0, or > 1000, defaults to 1000.
- """
- offset = int(req.GET.get('offset', 0))
- limit = int(req.GET.get('limit', 0))
- if not limit:
- limit = 1000
- limit = min(1000, limit)
- range_end = offset + limit
- return items[offset:range_end]
diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py
index fcda97ab1..1dfdd5318 100644
--- a/nova/api/openstack/auth.py
+++ b/nova/api/openstack/auth.py
@@ -1,3 +1,20 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack LLC.
+# 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
+
import datetime
import hashlib
import json
@@ -7,29 +24,46 @@ import webob.exc
import webob.dec
from nova import auth
+from nova import context
from nova import db
from nova import flags
from nova import manager
from nova import utils
+from nova import wsgi
from nova.api.openstack import faults
FLAGS = flags.FLAGS
-class Context(object):
- pass
-
-
-class BasicApiAuthManager(object):
- """ Implements a somewhat rudimentary version of OpenStack Auth"""
+class AuthMiddleware(wsgi.Middleware):
+ """Authorize the openstack API request or return an HTTP Forbidden."""
- def __init__(self, db_driver=None):
+ def __init__(self, application, db_driver=None):
if not db_driver:
db_driver = FLAGS.db_driver
self.db = utils.import_object(db_driver)
self.auth = auth.manager.AuthManager()
- self.context = Context()
- super(BasicApiAuthManager, self).__init__()
+ super(AuthMiddleware, self).__init__(application)
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ if not self.has_authentication(req):
+ return self.authenticate(req)
+
+ user = self.get_user_by_authentication(req)
+
+ if not user:
+ return faults.Fault(webob.exc.HTTPUnauthorized())
+
+ project = self.auth.get_project(FLAGS.default_project)
+ req.environ['nova.context'] = context.RequestContext(user, project)
+ return self.application
+
+ def has_authentication(self, req):
+ return 'X-Auth-Token' in req.headers
+
+ def get_user_by_authentication(self, req):
+ return self.authorize_token(req.headers["X-Auth-Token"])
def authenticate(self, req):
# Unless the request is explicitly made against /<version>/ don't
@@ -68,11 +102,12 @@ class BasicApiAuthManager(object):
This method will also remove the token if the timestamp is older than
2 days ago.
"""
- token = self.db.auth_get_token(self.context, token_hash)
+ ctxt = context.get_admin_context()
+ token = self.db.auth_get_token(ctxt, token_hash)
if token:
delta = datetime.datetime.now() - token.created_at
if delta.days >= 2:
- self.db.auth_destroy_token(self.context, token)
+ self.db.auth_destroy_token(ctxt, token)
else:
return self.auth.get_user(token.user_id)
return None
@@ -84,6 +119,7 @@ class BasicApiAuthManager(object):
key - string API key
req - webob.Request object
"""
+ ctxt = context.get_admin_context()
user = self.auth.get_user_from_access_key(key)
if user and user.name == username:
token_hash = hashlib.sha1('%s%s%f' % (username, key,
@@ -95,6 +131,6 @@ class BasicApiAuthManager(object):
token_dict['server_management_url'] = req.url
token_dict['storage_url'] = ''
token_dict['user_id'] = user.id
- token = self.db.auth_create_token(self.context, token_dict)
+ token = self.db.auth_create_token(ctxt, token_dict)
return token, user
return None, None
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
new file mode 100644
index 000000000..ac0572c96
--- /dev/null
+++ b/nova/api/openstack/common.py
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack LLC.
+# 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.
+
+
+def limited(items, req):
+ """Return a slice of items according to requested offset and limit.
+
+ items - a sliceable
+ req - wobob.Request possibly containing offset and limit GET variables.
+ offset is where to start in the list, and limit is the maximum number
+ of items to return.
+
+ If limit is not specified, 0, or > 1000, defaults to 1000.
+ """
+
+ offset = int(req.GET.get('offset', 0))
+ limit = int(req.GET.get('limit', 0))
+ if not limit:
+ limit = 1000
+ limit = min(1000, limit)
+ range_end = offset + limit
+ return items[offset:range_end]
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index f23f74fd1..f620d4107 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -18,6 +18,7 @@
from webob import exc
from nova.api.openstack import faults
+from nova.api.openstack import common
from nova.compute import instance_types
from nova import wsgi
import nova.api.openstack
@@ -39,7 +40,7 @@ class Controller(wsgi.Controller):
def detail(self, req):
"""Return all flavors in detail."""
items = [self.show(req, id)['flavor'] for id in self._all_ids()]
- items = nova.api.openstack.limited(items, req)
+ items = common.limited(items, req)
return dict(flavors=items)
def show(self, req, id):
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index de072b28f..8a6772fa5 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -22,6 +22,8 @@ from nova import utils
from nova import wsgi
import nova.api.openstack
import nova.image.service
+
+from nova.api.openstack import common
from nova.api.openstack import faults
@@ -49,11 +51,11 @@ class Controller(wsgi.Controller):
ctxt = req.environ['nova.context']
try:
images = self._service.detail(ctxt)
- images = nova.api.openstack.limited(images, req)
+ images = common.limited(images, req)
except NotImplementedError:
# Emulate detail() using repeated calls to show()
images = self._service.index(ctxt)
- images = nova.api.openstack.limited(images, req)
+ images = common.limited(images, req)
images = [self._service.show(ctxt, i['id']) for i in images]
return dict(images=images)
diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index 918caf055..91a8b2e55 100644
--- a/nova/api/openstack/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
@@ -1,3 +1,20 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack LLC.
+# 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
+
"""Rate limiting of arbitrary actions."""
import httplib
@@ -6,6 +23,8 @@ import urllib
import webob.dec
import webob.exc
+from nova import wsgi
+from nova.api.openstack import faults
# Convenience constants for the limits dictionary passed to Limiter().
PER_SECOND = 1
@@ -14,6 +33,83 @@ PER_HOUR = 60 * 60
PER_DAY = 60 * 60 * 24
+class RateLimitingMiddleware(wsgi.Middleware):
+ """Rate limit incoming requests according to the OpenStack rate limits."""
+
+ def __init__(self, application, service_host=None):
+ """Create a rate limiting middleware that wraps the given application.
+
+ By default, rate counters are stored in memory. If service_host is
+ specified, the middleware instead relies on the ratelimiting.WSGIApp
+ at the given host+port to keep rate counters.
+ """
+ if not service_host:
+ #TODO(gundlach): These limits were based on limitations of Cloud
+ #Servers. We should revisit them in Nova.
+ self.limiter = Limiter(limits={
+ 'DELETE': (100, PER_MINUTE),
+ 'PUT': (10, PER_MINUTE),
+ 'POST': (10, PER_MINUTE),
+ 'POST servers': (50, PER_DAY),
+ 'GET changes-since': (3, PER_MINUTE),
+ })
+ else:
+ self.limiter = WSGIAppProxy(service_host)
+ super(RateLimitingMiddleware, self).__init__(application)
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ """Rate limit the request.
+
+ If the request should be rate limited, return a 413 status with a
+ Retry-After header giving the time when the request would succeed.
+ """
+ return self.limited_request(req, self.application)
+
+ def limited_request(self, req, application):
+ """Rate limit the request.
+
+ If the request should be rate limited, return a 413 status with a
+ Retry-After header giving the time when the request would succeed.
+ """
+ action_name = self.get_action_name(req)
+ if not action_name:
+ # Not rate limited
+ return application
+ delay = self.get_delay(action_name,
+ req.environ['nova.context'].user_id)
+ if delay:
+ # TODO(gundlach): Get the retry-after format correct.
+ exc = webob.exc.HTTPRequestEntityTooLarge(
+ explanation=('Too many requests.'),
+ headers={'Retry-After': time.time() + delay})
+ raise faults.Fault(exc)
+ return application
+
+ def get_delay(self, action_name, username):
+ """Return the delay for the given action and username, or None if
+ the action would not be rate limited.
+ """
+ if action_name == 'POST servers':
+ # "POST servers" is a POST, so it counts against "POST" too.
+ # Attempt the "POST" first, lest we are rate limited by "POST" but
+ # use up a precious "POST servers" call.
+ delay = self.limiter.perform("POST", username=username)
+ if delay:
+ return delay
+ return self.limiter.perform(action_name, username=username)
+
+ def get_action_name(self, req):
+ """Return the action name for this request."""
+ if req.method == 'GET' and 'changes-since' in req.GET:
+ return 'GET changes-since'
+ if req.method == 'POST' and req.path_info.startswith('/servers'):
+ return 'POST servers'
+ if req.method in ['PUT', 'POST', 'DELETE']:
+ return req.method
+ return None
+
+
class Limiter(object):
"""Class providing rate limiting of arbitrary actions."""
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 5c3322f7c..8d60e2cab 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -22,6 +22,7 @@ from webob import exc
from nova import exception
from nova import wsgi
+from nova.api.openstack import common
from nova.api.openstack import faults
from nova.auth import manager as auth_manager
from nova.compute import api as compute_api
@@ -98,7 +99,7 @@ class Controller(wsgi.Controller):
"""
instance_list = self.compute_api.get_instances(
req.environ['nova.context'])
- limited_list = nova.api.openstack.limited(instance_list, req)
+ limited_list = common.limited(instance_list, req)
res = [entity_maker(inst)['server'] for inst in limited_list]
return _entity_list(res)
diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py
index e805ca9f7..75d02905c 100644
--- a/nova/api/openstack/sharedipgroups.py
+++ b/nova/api/openstack/sharedipgroups.py
@@ -19,4 +19,22 @@ from nova import wsgi
class Controller(wsgi.Controller):
- pass
+ """ The Shared IP Groups Controller for the Openstack API """
+
+ def index(self, req):
+ raise NotImplementedError
+
+ def show(self, req, id):
+ raise NotImplementedError
+
+ def update(self, req, id):
+ raise NotImplementedError
+
+ def delete(self, req, id):
+ raise NotImplementedError
+
+ def detail(self, req):
+ raise NotImplementedError
+
+ def create(self, req):
+ raise NotImplementedError