summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorTodd Willey <todd@ansolabs.com>2010-09-28 23:38:32 -0400
committerTodd Willey <todd@ansolabs.com>2010-09-28 23:38:32 -0400
commitb784836118d5900330c76863decd504ec7bd6a77 (patch)
tree8afdc39565488b872ed5360da340ea8284ad5ec1 /nova/api
parentc4df3d63c83c073664a9ed0abaefe2adbe4cd061 (diff)
parent3ebea539c25f913a22f86e7dd500bf5d7771614f (diff)
downloadnova-b784836118d5900330c76863decd504ec7bd6a77.tar.gz
nova-b784836118d5900330c76863decd504ec7bd6a77.tar.xz
nova-b784836118d5900330c76863decd504ec7bd6a77.zip
Merge trunk and fix test.
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/ec2/cloud.py69
-rw-r--r--nova/api/rackspace/__init__.py18
-rw-r--r--nova/api/rackspace/_id_translator.py2
-rw-r--r--nova/api/rackspace/auth.py8
-rw-r--r--nova/api/rackspace/backup_schedules.py (renamed from nova/api/rackspace/base.py)26
-rw-r--r--nova/api/rackspace/flavors.py4
-rw-r--r--nova/api/rackspace/images.py9
-rw-r--r--nova/api/rackspace/servers.py203
-rw-r--r--nova/api/rackspace/sharedipgroups.py4
9 files changed, 248 insertions, 95 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 528380f0f..d3f54367b 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -101,9 +101,9 @@ class CloudController(object):
def _get_mpi_data(self, project_id):
result = {}
- for instance in db.instance_get_by_project(None, project_id):
+ for instance in db.instance_get_all_by_project(None, project_id):
if instance['fixed_ip']:
- line = '%s slots=%d' % (instance['fixed_ip']['str_id'],
+ line = '%s slots=%d' % (instance['fixed_ip']['address'],
INSTANCE_TYPES[instance['instance_type']]['vcpus'])
key = str(instance['key_name'])
if key in result:
@@ -143,7 +143,7 @@ class CloudController(object):
},
'hostname': hostname,
'instance-action': 'none',
- 'instance-id': instance_ref['str_id'],
+ 'instance-id': instance_ref['ec2_id'],
'instance-type': instance_ref['instance_type'],
'local-hostname': hostname,
'local-ipv4': address,
@@ -245,7 +245,7 @@ class CloudController(object):
def get_console_output(self, context, instance_id, **kwargs):
# instance_id is passed in as a list of instances
- instance_ref = db.instance_get_by_str(context, instance_id[0])
+ instance_ref = db.instance_get_by_ec2_id(context, instance_id[0])
return rpc.call('%s.%s' % (FLAGS.compute_topic,
instance_ref['host']),
{"method": "get_console_output",
@@ -256,7 +256,7 @@ class CloudController(object):
if context.user.is_admin():
volumes = db.volume_get_all(context)
else:
- volumes = db.volume_get_by_project(context, context.project.id)
+ volumes = db.volume_get_all_by_project(context, context.project.id)
volumes = [self._format_volume(context, v) for v in volumes]
@@ -264,7 +264,7 @@ class CloudController(object):
def _format_volume(self, context, volume):
v = {}
- v['volumeId'] = volume['str_id']
+ v['volumeId'] = volume['ec2_id']
v['status'] = volume['status']
v['size'] = volume['size']
v['availabilityZone'] = volume['availability_zone']
@@ -282,7 +282,7 @@ class CloudController(object):
'device': volume['mountpoint'],
'instanceId': volume['instance_id'],
'status': 'attached',
- 'volume_id': volume['str_id']}]
+ 'volume_id': volume['ec2_id']}]
else:
v['attachmentSet'] = [{}]
@@ -319,13 +319,13 @@ class CloudController(object):
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
- volume_ref = db.volume_get_by_str(context, volume_id)
+ volume_ref = db.volume_get_by_ec2_id(context, volume_id)
# TODO(vish): abstract status checking?
if volume_ref['status'] != "available":
raise exception.ApiError("Volume status must be available")
if volume_ref['attach_status'] == "attached":
raise exception.ApiError("Volume is already attached")
- instance_ref = db.instance_get_by_str(context, instance_id)
+ instance_ref = db.instance_get_by_ec2_id(context, instance_id)
host = instance_ref['host']
rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "attach_volume",
@@ -341,7 +341,7 @@ class CloudController(object):
'volumeId': volume_ref['id']}
def detach_volume(self, context, volume_id, **kwargs):
- volume_ref = db.volume_get_by_str(context, volume_id)
+ volume_ref = db.volume_get_by_ec2_id(context, volume_id)
instance_ref = db.volume_get_instance(context, volume_ref['id'])
if not instance_ref:
raise exception.ApiError("Volume isn't attached to anything!")
@@ -361,7 +361,7 @@ class CloudController(object):
db.volume_detached(context)
return {'attachTime': volume_ref['attach_time'],
'device': volume_ref['mountpoint'],
- 'instanceId': instance_ref['str_id'],
+ 'instanceId': instance_ref['ec2_id'],
'requestId': context.request_id,
'status': volume_ref['attach_status'],
'volumeId': volume_ref['id']}
@@ -397,20 +397,20 @@ class CloudController(object):
def _format_instances(self, context, reservation_id=None):
reservations = {}
if reservation_id:
- instances = db.instance_get_by_reservation(context,
- reservation_id)
+ instances = db.instance_get_all_by_reservation(context,
+ reservation_id)
else:
if context.user.is_admin():
instances = db.instance_get_all(context)
else:
- instances = db.instance_get_by_project(context,
- context.project.id)
+ instances = db.instance_get_all_by_project(context,
+ context.project.id)
for instance in instances:
if not context.user.is_admin():
if instance['image_id'] == FLAGS.vpn_image_id:
continue
i = {}
- i['instanceId'] = instance['str_id']
+ i['instanceId'] = instance['ec2_id']
i['imageId'] = instance['image_id']
i['instanceState'] = {
'code': instance['state'],
@@ -419,10 +419,10 @@ class CloudController(object):
fixed_addr = None
floating_addr = None
if instance['fixed_ip']:
- fixed_addr = instance['fixed_ip']['str_id']
+ fixed_addr = instance['fixed_ip']['address']
if instance['fixed_ip']['floating_ips']:
fixed = instance['fixed_ip']
- floating_addr = fixed['floating_ips'][0]['str_id']
+ floating_addr = fixed['floating_ips'][0]['address']
i['privateDnsName'] = fixed_addr
i['publicDnsName'] = floating_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
@@ -456,14 +456,14 @@ class CloudController(object):
if context.user.is_admin():
iterator = db.floating_ip_get_all(context)
else:
- iterator = db.floating_ip_get_by_project(context,
- context.project.id)
+ iterator = db.floating_ip_get_all_by_project(context,
+ context.project.id)
for floating_ip_ref in iterator:
- address = floating_ip_ref['str_id']
+ address = floating_ip_ref['address']
instance_id = None
if (floating_ip_ref['fixed_ip']
and floating_ip_ref['fixed_ip']['instance']):
- instance_id = floating_ip_ref['fixed_ip']['instance']['str_id']
+ instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id']
address_rv = {'public_ip': address,
'instance_id': instance_id}
if context.user.is_admin():
@@ -494,19 +494,20 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "deallocate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id']}})
+ "floating_address": floating_ip_ref['address']}})
return {'releaseResponse': ["Address released."]}
def associate_address(self, context, instance_id, public_ip, **kwargs):
- instance_ref = db.instance_get_by_str(context, instance_id)
- fixed_ip_ref = db.fixed_ip_get_by_instance(context, instance_ref['id'])
+ instance_ref = db.instance_get_by_ec2_id(context, instance_id)
+ 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)
rpc.cast(network_topic,
{"method": "associate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id'],
- "fixed_address": fixed_ip_ref['str_id']}})
+ "floating_address": floating_ip_ref['address'],
+ "fixed_address": fixed_address}})
return {'associateResponse': ["Address associated."]}
def disassociate_address(self, context, public_ip, **kwargs):
@@ -515,7 +516,7 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "disassociate_floating_ip",
"args": {"context": None,
- "floating_address": floating_ip_ref['str_id']}})
+ "floating_address": floating_ip_ref['address']}})
return {'disassociateResponse': ["Address disassociated."]}
def _get_network_topic(self, context):
@@ -608,7 +609,7 @@ class CloudController(object):
inst = {}
inst['mac_address'] = utils.generate_mac()
inst['launch_index'] = num
- inst['hostname'] = instance_ref['str_id']
+ inst['hostname'] = instance_ref['ec2_id']
db.instance_update(context, inst_id, inst)
address = self.network_manager.allocate_fixed_ip(context,
inst_id,
@@ -637,7 +638,7 @@ class CloudController(object):
for id_str in instance_id:
logging.debug("Going to try and terminate %s" % id_str)
try:
- instance_ref = db.instance_get_by_str(context, id_str)
+ instance_ref = db.instance_get_by_ec2_id(context, id_str)
except exception.NotFound:
logging.warning("Instance %s was not found during terminate"
% id_str)
@@ -659,7 +660,7 @@ class CloudController(object):
rpc.cast(network_topic,
{"method": "disassociate_floating_ip",
"args": {"context": None,
- "address": address}})
+ "floating_address": address}})
address = db.instance_get_fixed_address(context,
instance_ref['id'])
@@ -683,7 +684,7 @@ class CloudController(object):
def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids"""
for id_str in instance_id:
- instance_ref = db.instance_get_by_str(context, id_str)
+ instance_ref = db.instance_get_by_ec2_id(context, id_str)
host = instance_ref['host']
rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "reboot_instance",
@@ -699,13 +700,13 @@ class CloudController(object):
changes[field] = kwargs[field]
if changes:
db_context = {}
- inst = db.instance_get_by_str(db_context, instance_id)
+ inst = db.instance_get_by_ec2_id(db_context, instance_id)
db.instance_update(db_context, inst['id'], kwargs)
return True
def delete_volume(self, context, volume_id, **kwargs):
# TODO: return error if not authorized
- volume_ref = db.volume_get_by_str(context, volume_id)
+ volume_ref = db.volume_get_by_ec2_id(context, volume_id)
if volume_ref['status'] != "available":
raise exception.ApiError("Volume status must be available")
now = datetime.datetime.utcnow()
diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py
index c24d08585..98802663f 100644
--- a/nova/api/rackspace/__init__.py
+++ b/nova/api/rackspace/__init__.py
@@ -31,6 +31,7 @@ import webob
from nova import flags
from nova import utils
from nova import wsgi
+from nova.api.rackspace import backup_schedules
from nova.api.rackspace import flavors
from nova.api.rackspace import images
from nova.api.rackspace import ratelimiting
@@ -67,8 +68,10 @@ class AuthMiddleware(wsgi.Middleware):
if not user:
return webob.exc.HTTPUnauthorized()
- context = {'user': user}
- req.environ['nova.context'] = context
+
+ if not req.environ.has_key('nova.context'):
+ req.environ['nova.context'] = {}
+ req.environ['nova.context']['user'] = user
return self.application
class RateLimitingMiddleware(wsgi.Middleware):
@@ -145,11 +148,20 @@ class APIRouter(wsgi.Router):
def __init__(self):
mapper = routes.Mapper()
- mapper.resource("server", "servers", controller=servers.Controller())
+ mapper.resource("server", "servers", controller=servers.Controller(),
+ collection={ 'detail': 'GET'},
+ member={'action':'POST'})
+
+ mapper.resource("backup_schedule", "backup_schedules",
+ controller=backup_schedules.Controller(),
+ parent_resource=dict(member_name='server',
+ collection_name = 'servers'))
+
mapper.resource("image", "images", controller=images.Controller(),
collection={'detail': 'GET'})
mapper.resource("flavor", "flavors", controller=flavors.Controller(),
collection={'detail': 'GET'})
mapper.resource("sharedipgroup", "sharedipgroups",
controller=sharedipgroups.Controller())
+
super(APIRouter, self).__init__(mapper)
diff --git a/nova/api/rackspace/_id_translator.py b/nova/api/rackspace/_id_translator.py
index aec5fb6a5..333aa8434 100644
--- a/nova/api/rackspace/_id_translator.py
+++ b/nova/api/rackspace/_id_translator.py
@@ -37,6 +37,6 @@ class RackspaceAPIIdTranslator(object):
# every int id be used.)
return int(self._store.hget(self._fwd_key, str(opaque_id)))
- def from_rs_id(self, strategy_name, rs_id):
+ def from_rs_id(self, rs_id):
"""Convert a Rackspace id to a strategy-specific one."""
return self._store.hget(self._rev_key, rs_id)
diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py
index ce5a967eb..8bfb0753e 100644
--- a/nova/api/rackspace/auth.py
+++ b/nova/api/rackspace/auth.py
@@ -1,13 +1,15 @@
import datetime
+import hashlib
import json
import time
+
import webob.exc
import webob.dec
-import hashlib
-from nova import flags
+
from nova import auth
-from nova import manager
from nova import db
+from nova import flags
+from nova import manager
from nova import utils
FLAGS = flags.FLAGS
diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/backup_schedules.py
index dd2c6543c..46da778ee 100644
--- a/nova/api/rackspace/base.py
+++ b/nova/api/rackspace/backup_schedules.py
@@ -15,16 +15,24 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nova import wsgi
+import time
+from webob import exc
+from nova import wsgi
+from nova.api.rackspace import _id_translator
+import nova.image.service
class Controller(wsgi.Controller):
- """TODO(eday): Base controller for all rackspace controllers. What is this
- for? Is this just Rackspace specific? """
+ def __init__(self):
+ pass
+
+ def index(self, req, server_id):
+ return exc.HTTPNotFound()
+
+ def create(self, req, server_id):
+ """ No actual update method required, since the existing API allows
+ both create and update through a POST """
+ return exc.HTTPNotFound()
- @classmethod
- def render(cls, instance):
- if isinstance(instance, list):
- return {cls.entity_name: cls.render(instance)}
- else:
- return {"TODO": "TODO"}
+ def delete(self, req, server_id):
+ return exc.HTTPNotFound()
diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py
index 60b35c939..3bcf170e5 100644
--- a/nova/api/rackspace/flavors.py
+++ b/nova/api/rackspace/flavors.py
@@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-from nova.api.rackspace import base
from nova.compute import instance_types
+from nova import wsgi
from webob import exc
-class Controller(base.Controller):
+class Controller(wsgi.Controller):
"""Flavor controller for the Rackspace API."""
_serialization_metadata = {
diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py
index 2f3e928b9..11b058dec 100644
--- a/nova/api/rackspace/images.py
+++ b/nova/api/rackspace/images.py
@@ -15,12 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import nova.image.service
-from nova.api.rackspace import base
-from nova.api.rackspace import _id_translator
from webob import exc
-class Controller(base.Controller):
+from nova import wsgi
+from nova.api.rackspace import _id_translator
+import nova.image.service
+
+class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py
index 1815f7523..4ab04bde7 100644
--- a/nova/api/rackspace/servers.py
+++ b/nova/api/rackspace/servers.py
@@ -14,67 +14,194 @@
# 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 time
-from nova import db
+from webob import exc
+
from nova import flags
from nova import rpc
from nova import utils
-from nova.api.rackspace import base
+from nova import wsgi
+from nova.api.rackspace import _id_translator
+from nova.compute import power_state
+import nova.image.service
FLAGS = flags.FLAGS
-class Controller(base.Controller):
- entity_name = 'servers'
- def index(self, **kwargs):
- instances = []
- for inst in db.instance_get_all(None):
- instances.append(instance_details(inst))
- def show(self, **kwargs):
- instance_id = kwargs['id']
- return db.instance_get(None, instance_id)
+def translator_instance():
+ """ Helper method for initializing the image id translator """
+ service = nova.image.service.ImageService.load()
+ return _id_translator.RackspaceAPIIdTranslator(
+ "image", service.__class__.__name__)
- def delete(self, **kwargs):
- instance_id = kwargs['id']
- instance = db.instance_get(None, instance_id)
- if not instance:
- raise ServerNotFound("The requested server was not found")
- instance.destroy()
- return True
+def _filter_params(inst_dict):
+ """ Extracts all updatable parameters for a server update request """
+ keys = ['name', 'adminPass']
+ new_attrs = {}
+ for k in keys:
+ if inst_dict.has_key(k):
+ new_attrs[k] = inst_dict[k]
+ return new_attrs
+
+def _entity_list(entities):
+ """ Coerces a list of servers into proper dictionary format """
+ return dict(servers=entities)
+
+def _entity_detail(inst):
+ """ Maps everything to Rackspace-like attributes for return"""
+ power_mapping = {
+ power_state.NOSTATE: 'build',
+ power_state.RUNNING: 'active',
+ power_state.BLOCKED: 'active',
+ power_state.PAUSED: 'suspended',
+ power_state.SHUTDOWN: 'active',
+ power_state.SHUTOFF: 'active',
+ power_state.CRASHED: 'error'
+ }
+ inst_dict = {}
+
+ mapped_keys = dict(status='state', imageId='image_id',
+ flavorId='instance_type', name='server_name', id='id')
+
+ for k, v in mapped_keys.iteritems():
+ inst_dict[k] = inst[v]
+
+ inst_dict['status'] = power_mapping[inst_dict['status']]
+ inst_dict['addresses'] = dict(public=[], private=[])
+ inst_dict['metadata'] = {}
+ inst_dict['hostId'] = ''
+
+ return dict(server=inst_dict)
+
+def _entity_inst(inst):
+ """ Filters all model attributes save for id and name """
+ return dict(server=dict(id=inst['id'], name=inst['server_name']))
+
+class Controller(wsgi.Controller):
+ """ The Server API controller for the Openstack API """
+
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "server": [ "id", "imageId", "name", "flavorId", "hostId",
+ "status", "progress", "progress" ]
+ }
+ }
+ }
+
+ def __init__(self, db_driver=None):
+ if not db_driver:
+ db_driver = FLAGS.db_driver
+ self.db_driver = utils.import_object(db_driver)
+ super(Controller, self).__init__()
+
+ def index(self, req):
+ """ Returns a list of server names and ids for a given user """
+ user_id = req.environ['nova.context']['user']['id']
+ instance_list = self.db_driver.instance_get_all_by_user(None, user_id)
+ res = [_entity_inst(inst)['server'] for inst in instance_list]
+ return _entity_list(res)
+
+ def detail(self, req):
+ """ Returns a list of server details for a given user """
+ user_id = req.environ['nova.context']['user']['id']
+ res = [_entity_detail(inst)['server'] for inst in
+ self.db_driver.instance_get_all_by_user(None, user_id)]
+ return _entity_list(res)
+
+ def show(self, req, id):
+ """ Returns server details by server id """
+ user_id = req.environ['nova.context']['user']['id']
+ inst = self.db_driver.instance_get(None, id)
+ if inst:
+ if inst.user_id == user_id:
+ return _entity_detail(inst)
+ raise exc.HTTPNotFound()
+
+ def delete(self, req, id):
+ """ Destroys a server """
+ user_id = req.environ['nova.context']['user']['id']
+ instance = self.db_driver.instance_get(None, id)
+ if instance and instance['user_id'] == user_id:
+ self.db_driver.instance_destroy(None, id)
+ return exc.HTTPAccepted()
+ return exc.HTTPNotFound()
+
+ def create(self, req):
+ """ Creates a new server for a given user """
+ if not req.environ.has_key('inst_dict'):
+ return exc.HTTPUnprocessableEntity()
+
+ inst = self._build_server_instance(req)
- def create(self, **kwargs):
- inst = self.build_server_instance(kwargs['server'])
rpc.cast(
FLAGS.compute_topic, {
"method": "run_instance",
"args": {"instance_id": inst['id']}})
+ return _entity_inst(inst)
- def update(self, **kwargs):
- instance_id = kwargs['id']
- instance = db.instance_get(None, instance_id)
+ def update(self, req, id):
+ """ Updates the server name or password """
+ if not req.environ.has_key('inst_dict'):
+ return exc.HTTPUnprocessableEntity()
+
+ instance = self.db_driver.instance_get(None, id)
if not instance:
- raise ServerNotFound("The requested server was not found")
- instance.update(kwargs['server'])
- instance.save()
+ return exc.HTTPNotFound()
+
+ attrs = req.environ['nova.context'].get('model_attributes', None)
+ if attrs:
+ self.db_driver.instance_update(None, id, _filter_params(attrs))
+ return exc.HTTPNoContent()
+
+ def action(self, req, id):
+ """ multi-purpose method used to reboot, rebuild, and
+ resize a server """
+ if not req.environ.has_key('inst_dict'):
+ return exc.HTTPUnprocessableEntity()
- def build_server_instance(self, env):
+ def _build_server_instance(self, req):
"""Build instance data structure and save it to the data store."""
- reservation = utils.generate_uid('r')
ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
inst = {}
- inst['name'] = env['server']['name']
- inst['image_id'] = env['server']['imageId']
+
+ env = req.environ['inst_dict']
+
+ image_id = env['server']['imageId']
+ opaque_id = translator_instance().from_rs_id(image_id)
+
+ inst['name'] = env['server']['server_name']
+ inst['image_id'] = opaque_id
inst['instance_type'] = env['server']['flavorId']
- inst['user_id'] = env['user']['id']
- inst['project_id'] = env['project']['id']
- inst['reservation_id'] = reservation
+
+ user_id = req.environ['nova.context']['user']['id']
+ inst['user_id'] = user_id
+
inst['launch_time'] = ltime
inst['mac_address'] = utils.generate_mac()
- inst_id = db.instance_create(None, inst)['id']
- address = self.network_manager.allocate_fixed_ip(None, inst_id)
- # key_data, key_name, ami_launch_index
- # TODO(todd): key data or root password
- inst.save()
+
+ inst['project_id'] = env['project']['id']
+ inst['reservation_id'] = reservation
+ reservation = utils.generate_uid('r')
+
+ address = self.network.allocate_ip(
+ inst['user_id'],
+ inst['project_id'],
+ mac=inst['mac_address'])
+
+ inst['private_dns_name'] = str(address)
+ inst['bridge_name'] = network.BridgedNetwork.get_network_for_project(
+ inst['user_id'],
+ inst['project_id'],
+ 'default')['bridge_name']
+
+ ref = self.db_driver.instance_create(None, inst)
+ inst['id'] = ref.id
+
return inst
+
+
diff --git a/nova/api/rackspace/sharedipgroups.py b/nova/api/rackspace/sharedipgroups.py
index 986f11434..4d2d0ede1 100644
--- a/nova/api/rackspace/sharedipgroups.py
+++ b/nova/api/rackspace/sharedipgroups.py
@@ -15,4 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-class Controller(object): pass
+from nova import wsgi
+
+class Controller(wsgi.Controller): pass