summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-08-31 23:21:30 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-08-31 23:21:30 -0700
commite7eb9443bc07a173b5885f634c80c616d9d59e9b (patch)
tree579aef2e8566adbf7d94ef82aa121120f3fdb37d /nova/api
parentad7a20231a8fb11bf7c75f2e180735e2de450102 (diff)
parent975861fd0b8fe7c89ccb6a31b0d0c89948c18252 (diff)
downloadnova-e7eb9443bc07a173b5885f634c80c616d9d59e9b.tar.gz
nova-e7eb9443bc07a173b5885f634c80c616d9d59e9b.tar.xz
nova-e7eb9443bc07a173b5885f634c80c616d9d59e9b.zip
merged orm branch
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/__init__.py37
-rw-r--r--nova/api/ec2/__init__.py42
-rw-r--r--nova/api/rackspace/__init__.py83
-rw-r--r--nova/api/rackspace/_id_translator.py42
-rw-r--r--nova/api/rackspace/base.py30
-rw-r--r--nova/api/rackspace/flavors.py54
-rw-r--r--nova/api/rackspace/images.py70
-rw-r--r--nova/api/rackspace/notes.txt23
-rw-r--r--nova/api/rackspace/servers.py83
-rw-r--r--nova/api/rackspace/sharedipgroups.py18
-rw-r--r--nova/api/test.py61
11 files changed, 543 insertions, 0 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
new file mode 100644
index 000000000..b9b9e3988
--- /dev/null
+++ b/nova/api/__init__.py
@@ -0,0 +1,37 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Root WSGI middleware for all API controllers.
+"""
+
+import routes
+
+from nova import wsgi
+from nova.api import ec2
+from nova.api import rackspace
+
+
+class API(wsgi.Router):
+ """Routes top-level requests to the appropriate controller."""
+
+ def __init__(self):
+ mapper = routes.Mapper()
+ mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API())
+ mapper.connect("/ec2/{path_info:.*}", controller=ec2.API())
+ super(API, self).__init__(mapper)
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
new file mode 100644
index 000000000..6eec0abf7
--- /dev/null
+++ b/nova/api/ec2/__init__.py
@@ -0,0 +1,42 @@
+# 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.
+
+"""
+WSGI middleware for EC2 API controllers.
+"""
+
+import routes
+import webob.dec
+
+from nova import wsgi
+
+
+class API(wsgi.Router):
+ """Routes EC2 requests to the appropriate controller."""
+
+ def __init__(self):
+ mapper = routes.Mapper()
+ mapper.connect(None, "{all:.*}", controller=self.dummy)
+ super(API, self).__init__(mapper)
+
+ @staticmethod
+ @webob.dec.wsgify
+ def dummy(req):
+ """Temporary dummy controller."""
+ msg = "dummy response -- please hook up __init__() to cloud.py instead"
+ return repr({'dummy': msg,
+ 'kwargs': repr(req.environ['wsgiorg.routing_args'][1])})
diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py
new file mode 100644
index 000000000..b4d666d63
--- /dev/null
+++ b/nova/api/rackspace/__init__.py
@@ -0,0 +1,83 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+WSGI middleware for Rackspace API controllers.
+"""
+
+import json
+import time
+
+import routes
+import webob.dec
+import webob.exc
+
+from nova import flags
+from nova import wsgi
+from nova.api.rackspace import flavors
+from nova.api.rackspace import images
+from nova.api.rackspace import servers
+from nova.api.rackspace import sharedipgroups
+from nova.auth import manager
+
+
+class API(wsgi.Middleware):
+ """WSGI entry point for all Rackspace API requests."""
+
+ def __init__(self):
+ app = AuthMiddleware(APIRouter())
+ super(API, self).__init__(app)
+
+
+class AuthMiddleware(wsgi.Middleware):
+ """Authorize the rackspace API request or return an HTTP Forbidden."""
+
+ #TODO(gundlach): isn't this the old Nova API's auth? Should it be replaced
+ #with correct RS API auth?
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ context = {}
+ if "HTTP_X_AUTH_TOKEN" in req.environ:
+ context['user'] = manager.AuthManager().get_user_from_access_key(
+ req.environ['HTTP_X_AUTH_TOKEN'])
+ if context['user']:
+ context['project'] = manager.AuthManager().get_project(
+ context['user'].name)
+ if "user" not in context:
+ return webob.exc.HTTPForbidden()
+ req.environ['nova.context'] = context
+ return self.application
+
+
+class APIRouter(wsgi.Router):
+ """
+ Routes requests on the Rackspace API to the appropriate controller
+ and method.
+ """
+
+ def __init__(self):
+ mapper = routes.Mapper()
+ mapper.resource("server", "servers", controller=servers.Controller())
+ 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
new file mode 100644
index 000000000..aec5fb6a5
--- /dev/null
+++ b/nova/api/rackspace/_id_translator.py
@@ -0,0 +1,42 @@
+from nova import datastore
+
+class RackspaceAPIIdTranslator(object):
+ """
+ Converts Rackspace API ids to and from the id format for a given
+ strategy.
+ """
+
+ def __init__(self, id_type, service_name):
+ """
+ Creates a translator for ids of the given type (e.g. 'flavor'), for the
+ given storage service backend class name (e.g. 'LocalFlavorService').
+ """
+
+ self._store = datastore.Redis.instance()
+ key_prefix = "rsapi.idtranslator.%s.%s" % (id_type, service_name)
+ # Forward (strategy format -> RS format) and reverse translation keys
+ self._fwd_key = "%s.fwd" % key_prefix
+ self._rev_key = "%s.rev" % key_prefix
+
+ def to_rs_id(self, opaque_id):
+ """Convert an id from a strategy-specific one to a Rackspace one."""
+ result = self._store.hget(self._fwd_key, str(opaque_id))
+ if result: # we have a mapping from opaque to RS for this strategy
+ return int(result)
+ else:
+ # Store the mapping.
+ nextid = self._store.incr("%s.lastid" % self._fwd_key)
+ if self._store.hsetnx(self._fwd_key, str(opaque_id), nextid):
+ # If someone else didn't beat us to it, store the reverse
+ # mapping as well.
+ self._store.hset(self._rev_key, nextid, str(opaque_id))
+ return nextid
+ else:
+ # Someone beat us to it; use their number instead, and
+ # discard nextid (which is OK -- we don't require that
+ # 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):
+ """Convert a Rackspace id to a strategy-specific one."""
+ return self._store.hget(self._rev_key, rs_id)
diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/base.py
new file mode 100644
index 000000000..dd2c6543c
--- /dev/null
+++ b/nova/api/rackspace/base.py
@@ -0,0 +1,30 @@
+# 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.
+
+from nova import wsgi
+
+
+class Controller(wsgi.Controller):
+ """TODO(eday): Base controller for all rackspace controllers. What is this
+ for? Is this just Rackspace specific? """
+
+ @classmethod
+ def render(cls, instance):
+ if isinstance(instance, list):
+ return {cls.entity_name: cls.render(instance)}
+ else:
+ return {"TODO": "TODO"}
diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py
new file mode 100644
index 000000000..60b35c939
--- /dev/null
+++ b/nova/api/rackspace/flavors.py
@@ -0,0 +1,54 @@
+# 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.
+
+from nova.api.rackspace import base
+from nova.compute import instance_types
+from webob import exc
+
+class Controller(base.Controller):
+ """Flavor controller for the Rackspace API."""
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "flavor": [ "id", "name", "ram", "disk" ]
+ }
+ }
+ }
+
+ def index(self, req):
+ """Return all flavors in brief."""
+ return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
+ for flavor in self.detail(req)['flavors']])
+
+ def detail(self, req):
+ """Return all flavors in detail."""
+ items = [self.show(req, id)['flavor'] for id in self._all_ids()]
+ return dict(flavors=items)
+
+ def show(self, req, id):
+ """Return data about the given flavor id."""
+ for name, val in instance_types.INSTANCE_TYPES.iteritems():
+ if val['flavorid'] == int(id):
+ item = dict(ram=val['memory_mb'], disk=val['local_gb'],
+ id=val['flavorid'], name=name)
+ return dict(flavor=item)
+ raise exc.HTTPNotFound()
+
+ def _all_ids(self):
+ """Return the list of all flavorids."""
+ return [i['flavorid'] for i in instance_types.INSTANCE_TYPES.values()]
diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py
new file mode 100644
index 000000000..2f3e928b9
--- /dev/null
+++ b/nova/api/rackspace/images.py
@@ -0,0 +1,70 @@
+# 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 nova.image.service
+from nova.api.rackspace import base
+from nova.api.rackspace import _id_translator
+from webob import exc
+
+class Controller(base.Controller):
+
+ _serialization_metadata = {
+ 'application/xml': {
+ "attributes": {
+ "image": [ "id", "name", "updated", "created", "status",
+ "serverId", "progress" ]
+ }
+ }
+ }
+
+ def __init__(self):
+ self._service = nova.image.service.ImageService.load()
+ self._id_translator = _id_translator.RackspaceAPIIdTranslator(
+ "image", self._service.__class__.__name__)
+
+ def index(self, req):
+ """Return all public images in brief."""
+ return dict(images=[dict(id=img['id'], name=img['name'])
+ for img in self.detail(req)['images']])
+
+ def detail(self, req):
+ """Return all public images in detail."""
+ data = self._service.index()
+ for img in data:
+ img['id'] = self._id_translator.to_rs_id(img['id'])
+ return dict(images=data)
+
+ def show(self, req, id):
+ """Return data about the given image id."""
+ opaque_id = self._id_translator.from_rs_id(id)
+ img = self._service.show(opaque_id)
+ img['id'] = id
+ return dict(image=img)
+
+ def delete(self, req, id):
+ # Only public images are supported for now.
+ raise exc.HTTPNotFound()
+
+ def create(self, req):
+ # Only public images are supported for now, so a request to
+ # make a backup of a server cannot be supproted.
+ raise exc.HTTPNotFound()
+
+ def update(self, req, id):
+ # Users may not modify public images, and that's all that
+ # we support for now.
+ raise exc.HTTPNotFound()
diff --git a/nova/api/rackspace/notes.txt b/nova/api/rackspace/notes.txt
new file mode 100644
index 000000000..e133bf5ea
--- /dev/null
+++ b/nova/api/rackspace/notes.txt
@@ -0,0 +1,23 @@
+We will need:
+
+ImageService
+a service that can do crud on image information. not user-specific. opaque
+image ids.
+
+GlanceImageService(ImageService):
+image ids are URIs.
+
+LocalImageService(ImageService):
+image ids are random strings.
+
+RackspaceAPITranslationStore:
+translates RS server/images/flavor/etc ids into formats required
+by a given ImageService strategy.
+
+api.rackspace.images.Controller:
+uses an ImageService strategy behind the scenes to do its fetching; it just
+converts int image id into a strategy-specific image id.
+
+who maintains the mapping from user to [images he owns]? nobody, because
+we have no way of enforcing access to his images, without kryptex which
+won't be in Austin.
diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py
new file mode 100644
index 000000000..25d1fe9c8
--- /dev/null
+++ b/nova/api/rackspace/servers.py
@@ -0,0 +1,83 @@
+# 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.
+
+from nova import rpc
+from nova.compute import model as compute
+from nova.api.rackspace import base
+
+
+class Controller(base.Controller):
+ entity_name = 'servers'
+
+ def index(self, **kwargs):
+ instances = []
+ for inst in compute.InstanceDirectory().all:
+ instances.append(instance_details(inst))
+
+ def show(self, **kwargs):
+ instance_id = kwargs['id']
+ return compute.InstanceDirectory().get(instance_id)
+
+ def delete(self, **kwargs):
+ instance_id = kwargs['id']
+ instance = compute.InstanceDirectory().get(instance_id)
+ if not instance:
+ raise ServerNotFound("The requested server was not found")
+ instance.destroy()
+ return True
+
+ def create(self, **kwargs):
+ inst = self.build_server_instance(kwargs['server'])
+ rpc.cast(
+ FLAGS.compute_topic, {
+ "method": "run_instance",
+ "args": {"instance_id": inst.instance_id}})
+
+ def update(self, **kwargs):
+ instance_id = kwargs['id']
+ instance = compute.InstanceDirectory().get(instance_id)
+ if not instance:
+ raise ServerNotFound("The requested server was not found")
+ instance.update(kwargs['server'])
+ instance.save()
+
+ def build_server_instance(self, env):
+ """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 = self.instdir.new()
+ inst['name'] = env['server']['name']
+ inst['image_id'] = env['server']['imageId']
+ inst['instance_type'] = env['server']['flavorId']
+ inst['user_id'] = env['user']['id']
+ inst['project_id'] = env['project']['id']
+ inst['reservation_id'] = reservation
+ inst['launch_time'] = ltime
+ inst['mac_address'] = utils.generate_mac()
+ 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']
+ # key_data, key_name, ami_launch_index
+ # TODO(todd): key data or root password
+ inst.save()
+ return inst
diff --git a/nova/api/rackspace/sharedipgroups.py b/nova/api/rackspace/sharedipgroups.py
new file mode 100644
index 000000000..986f11434
--- /dev/null
+++ b/nova/api/rackspace/sharedipgroups.py
@@ -0,0 +1,18 @@
+# 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.
+
+class Controller(object): pass
diff --git a/nova/api/test.py b/nova/api/test.py
new file mode 100644
index 000000000..51b114b8e
--- /dev/null
+++ b/nova/api/test.py
@@ -0,0 +1,61 @@
+# 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.
+
+"""
+Test for the root WSGI middleware for all API controllers.
+"""
+
+import unittest
+
+import stubout
+import webob
+import webob.dec
+
+from nova import api
+
+
+class Test(unittest.TestCase):
+
+ def setUp(self): # pylint: disable-msg=C0103
+ self.stubs = stubout.StubOutForTesting()
+
+ def tearDown(self): # pylint: disable-msg=C0103
+ self.stubs.UnsetAll()
+
+ def test_rackspace(self):
+ self.stubs.Set(api.rackspace, 'API', APIStub)
+ result = webob.Request.blank('/v1.0/cloud').get_response(api.API())
+ self.assertEqual(result.body, "/cloud")
+
+ def test_ec2(self):
+ self.stubs.Set(api.ec2, 'API', APIStub)
+ result = webob.Request.blank('/ec2/cloud').get_response(api.API())
+ self.assertEqual(result.body, "/cloud")
+
+ def test_not_found(self):
+ self.stubs.Set(api.ec2, 'API', APIStub)
+ self.stubs.Set(api.rackspace, 'API', APIStub)
+ result = webob.Request.blank('/test/cloud').get_response(api.API())
+ self.assertNotEqual(result.body, "/cloud")
+
+
+class APIStub(object):
+ """Class to verify request and mark it was called."""
+
+ @webob.dec.wsgify
+ def __call__(self, req):
+ return req.path_info