From 1e403e56dc1147ce3feea1b8931948bc35f23a44 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 17 Aug 2010 16:43:37 -0700 Subject: In an effort to keep new and old API code separate, I've created a nova.api to put all new API code under. This means nova.endpoint only contains the old Tornado implementation. I also cleaned up a few pep8 and other style nits in the new API code. --- nova/api/__init__.py | 38 +++++++++++ nova/api/ec2/__init__.py | 42 ++++++++++++ nova/api/rackspace/__init__.py | 81 +++++++++++++++++++++++ nova/api/rackspace/controllers/__init__.py | 0 nova/api/rackspace/controllers/base.py | 30 +++++++++ nova/api/rackspace/controllers/flavors.py | 18 +++++ nova/api/rackspace/controllers/images.py | 18 +++++ nova/api/rackspace/controllers/servers.py | 83 ++++++++++++++++++++++++ nova/api/rackspace/controllers/sharedipgroups.py | 18 +++++ 9 files changed, 328 insertions(+) create mode 100644 nova/api/__init__.py create mode 100644 nova/api/ec2/__init__.py create mode 100644 nova/api/rackspace/__init__.py create mode 100644 nova/api/rackspace/controllers/__init__.py create mode 100644 nova/api/rackspace/controllers/base.py create mode 100644 nova/api/rackspace/controllers/flavors.py create mode 100644 nova/api/rackspace/controllers/images.py create mode 100644 nova/api/rackspace/controllers/servers.py create mode 100644 nova/api/rackspace/controllers/sharedipgroups.py (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py new file mode 100644 index 000000000..a6bb93348 --- /dev/null +++ b/nova/api/__init__.py @@ -0,0 +1,38 @@ +# 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(None, "/v1.0/{path_info:.*}", + controller=rackspace.API()) + mapper.connect(None, "/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..662cbe495 --- /dev/null +++ b/nova/api/rackspace/__init__.py @@ -0,0 +1,81 @@ +# 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.controllers import flavors +from nova.api.rackspace.controllers import images +from nova.api.rackspace.controllers import servers +from nova.api.rackspace.controllers 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()) + mapper.resource("flavor", "flavors", controller=flavors.Controller()) + mapper.resource("sharedipgroup", "sharedipgroups", + controller=sharedipgroups.Controller()) + super(APIRouter, self).__init__(mapper) diff --git a/nova/api/rackspace/controllers/__init__.py b/nova/api/rackspace/controllers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/rackspace/controllers/base.py b/nova/api/rackspace/controllers/base.py new file mode 100644 index 000000000..dd2c6543c --- /dev/null +++ b/nova/api/rackspace/controllers/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/controllers/flavors.py b/nova/api/rackspace/controllers/flavors.py new file mode 100644 index 000000000..986f11434 --- /dev/null +++ b/nova/api/rackspace/controllers/flavors.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/rackspace/controllers/images.py b/nova/api/rackspace/controllers/images.py new file mode 100644 index 000000000..986f11434 --- /dev/null +++ b/nova/api/rackspace/controllers/images.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/rackspace/controllers/servers.py b/nova/api/rackspace/controllers/servers.py new file mode 100644 index 000000000..1911d5abf --- /dev/null +++ b/nova/api/rackspace/controllers/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.controllers import base + + +class Controller(base.Controller): + entity_name = 'servers' + + def index(self, **kwargs): + instanmces = [] + 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/controllers/sharedipgroups.py b/nova/api/rackspace/controllers/sharedipgroups.py new file mode 100644 index 000000000..986f11434 --- /dev/null +++ b/nova/api/rackspace/controllers/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 -- cgit From 67ea462eadcc02ca2f8244062c786bd98871e9e8 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 17 Aug 2010 23:46:16 -0700 Subject: Added unittests for wsgi and api. --- nova/api/__init__.py | 5 ++-- nova/api/test.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 nova/api/test.py (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index a6bb93348..b9b9e3988 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -32,7 +32,6 @@ class API(wsgi.Router): def __init__(self): mapper = routes.Mapper() - mapper.connect(None, "/v1.0/{path_info:.*}", - controller=rackspace.API()) - mapper.connect(None, "/ec2/{path_info:.*}", controller=ec2.API()) + 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/test.py b/nova/api/test.py new file mode 100644 index 000000000..09f79c02e --- /dev/null +++ b/nova/api/test.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. + +""" +Test for the root WSGI middleware for all API controllers. +""" + +import unittest + +import stubout + +from nova import api +from nova import wsgi_test + + +class Test(unittest.TestCase): + + def setUp(self): # pylint: disable-msg=C0103 + self.called = False + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): # pylint: disable-msg=C0103 + self.stubs.UnsetAll() + + def test_rackspace(self): + self.stubs.Set(api.rackspace, 'API', get_api_stub(self)) + api.API()(wsgi_test.get_environ({'PATH_INFO': '/v1.0/cloud'}), + wsgi_test.start_response) + self.assertTrue(self.called) + + def test_ec2(self): + self.stubs.Set(api.ec2, 'API', get_api_stub(self)) + api.API()(wsgi_test.get_environ({'PATH_INFO': '/ec2/cloud'}), + wsgi_test.start_response) + self.assertTrue(self.called) + + def test_not_found(self): + self.stubs.Set(api.ec2, 'API', get_api_stub(self)) + self.stubs.Set(api.rackspace, 'API', get_api_stub(self)) + api.API()(wsgi_test.get_environ({'PATH_INFO': '/'}), + wsgi_test.start_response) + self.assertFalse(self.called) + + +def get_api_stub(test_object): + """Get a stub class that verifies next part of the request.""" + + class APIStub(object): + """Class to verify request and mark it was called.""" + test = test_object + + def __call__(self, environ, start_response): + self.test.assertEqual(environ['PATH_INFO'], '/cloud') + self.test.called = True + + return APIStub -- cgit From 43d2310f87a2f78f342b171de403f3db74a98295 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 18 Aug 2010 08:39:28 -0700 Subject: Fixed typo. --- nova/api/rackspace/controllers/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/controllers/servers.py b/nova/api/rackspace/controllers/servers.py index 1911d5abf..1d0221ea8 100644 --- a/nova/api/rackspace/controllers/servers.py +++ b/nova/api/rackspace/controllers/servers.py @@ -24,7 +24,7 @@ class Controller(base.Controller): entity_name = 'servers' def index(self, **kwargs): - instanmces = [] + instances = [] for inst in compute.InstanceDirectory().all: instances.append(instance_details(inst)) -- cgit From 4e5e72da2e3242026d757c8d5143e16f9d00cb6a Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 18 Aug 2010 08:56:33 -0700 Subject: Removed the 'controllers' directory under 'rackspace' due to full class name redundancy. --- nova/api/rackspace/__init__.py | 8 +-- nova/api/rackspace/base.py | 30 +++++++++ nova/api/rackspace/controllers/__init__.py | 0 nova/api/rackspace/controllers/base.py | 30 --------- nova/api/rackspace/controllers/flavors.py | 18 ----- nova/api/rackspace/controllers/images.py | 18 ----- nova/api/rackspace/controllers/servers.py | 83 ------------------------ nova/api/rackspace/controllers/sharedipgroups.py | 18 ----- nova/api/rackspace/flavors.py | 18 +++++ nova/api/rackspace/images.py | 18 +++++ nova/api/rackspace/servers.py | 83 ++++++++++++++++++++++++ nova/api/rackspace/sharedipgroups.py | 18 +++++ 12 files changed, 171 insertions(+), 171 deletions(-) create mode 100644 nova/api/rackspace/base.py delete mode 100644 nova/api/rackspace/controllers/__init__.py delete mode 100644 nova/api/rackspace/controllers/base.py delete mode 100644 nova/api/rackspace/controllers/flavors.py delete mode 100644 nova/api/rackspace/controllers/images.py delete mode 100644 nova/api/rackspace/controllers/servers.py delete mode 100644 nova/api/rackspace/controllers/sharedipgroups.py create mode 100644 nova/api/rackspace/flavors.py create mode 100644 nova/api/rackspace/images.py create mode 100644 nova/api/rackspace/servers.py create mode 100644 nova/api/rackspace/sharedipgroups.py (limited to 'nova/api') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 662cbe495..27e78f801 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -29,10 +29,10 @@ import webob.exc from nova import flags from nova import wsgi -from nova.api.rackspace.controllers import flavors -from nova.api.rackspace.controllers import images -from nova.api.rackspace.controllers import servers -from nova.api.rackspace.controllers import sharedipgroups +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 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/controllers/__init__.py b/nova/api/rackspace/controllers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/api/rackspace/controllers/base.py b/nova/api/rackspace/controllers/base.py deleted file mode 100644 index dd2c6543c..000000000 --- a/nova/api/rackspace/controllers/base.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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/controllers/flavors.py b/nova/api/rackspace/controllers/flavors.py deleted file mode 100644 index 986f11434..000000000 --- a/nova/api/rackspace/controllers/flavors.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/rackspace/controllers/images.py b/nova/api/rackspace/controllers/images.py deleted file mode 100644 index 986f11434..000000000 --- a/nova/api/rackspace/controllers/images.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/rackspace/controllers/servers.py b/nova/api/rackspace/controllers/servers.py deleted file mode 100644 index 1d0221ea8..000000000 --- a/nova/api/rackspace/controllers/servers.py +++ /dev/null @@ -1,83 +0,0 @@ -# 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.controllers 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/controllers/sharedipgroups.py b/nova/api/rackspace/controllers/sharedipgroups.py deleted file mode 100644 index 986f11434..000000000 --- a/nova/api/rackspace/controllers/sharedipgroups.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/rackspace/flavors.py b/nova/api/rackspace/flavors.py new file mode 100644 index 000000000..986f11434 --- /dev/null +++ b/nova/api/rackspace/flavors.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/rackspace/images.py b/nova/api/rackspace/images.py new file mode 100644 index 000000000..986f11434 --- /dev/null +++ b/nova/api/rackspace/images.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/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 -- cgit From 02592d584cc21e536574d20b01d8dbf82474bcd3 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 18 Aug 2010 15:00:20 -0700 Subject: Updated the tests to use webob, removed the 'called' thing and just use return values instead. --- nova/api/test.py | 43 +++++++++++++++++-------------------------- 1 file changed, 17 insertions(+), 26 deletions(-) (limited to 'nova/api') diff --git a/nova/api/test.py b/nova/api/test.py index 09f79c02e..51b114b8e 100644 --- a/nova/api/test.py +++ b/nova/api/test.py @@ -22,49 +22,40 @@ Test for the root WSGI middleware for all API controllers. import unittest import stubout +import webob +import webob.dec from nova import api -from nova import wsgi_test class Test(unittest.TestCase): def setUp(self): # pylint: disable-msg=C0103 - self.called = False self.stubs = stubout.StubOutForTesting() def tearDown(self): # pylint: disable-msg=C0103 self.stubs.UnsetAll() def test_rackspace(self): - self.stubs.Set(api.rackspace, 'API', get_api_stub(self)) - api.API()(wsgi_test.get_environ({'PATH_INFO': '/v1.0/cloud'}), - wsgi_test.start_response) - self.assertTrue(self.called) + 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', get_api_stub(self)) - api.API()(wsgi_test.get_environ({'PATH_INFO': '/ec2/cloud'}), - wsgi_test.start_response) - self.assertTrue(self.called) + 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', get_api_stub(self)) - self.stubs.Set(api.rackspace, 'API', get_api_stub(self)) - api.API()(wsgi_test.get_environ({'PATH_INFO': '/'}), - wsgi_test.start_response) - self.assertFalse(self.called) + 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") -def get_api_stub(test_object): - """Get a stub class that verifies next part of the request.""" +class APIStub(object): + """Class to verify request and mark it was called.""" - class APIStub(object): - """Class to verify request and mark it was called.""" - test = test_object - - def __call__(self, environ, start_response): - self.test.assertEqual(environ['PATH_INFO'], '/cloud') - self.test.called = True - - return APIStub + @webob.dec.wsgify + def __call__(self, req): + return req.path_info -- cgit From b651008e7e4f60f2ccb07497c27d866814156209 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Thu, 19 Aug 2010 16:05:27 -0400 Subject: Complete the Image API against a LocalImageService until Glance's API exists (at which point we'll make a GlanceImageService and make the choice of ImageService plugin configurable.) --- nova/api/rackspace/images.py | 83 +++++++++++++++++++++++++++++-------------- nova/api/rackspace/notes.txt | 23 ++++++++++++ nova/api/services/__init__.py | 0 nova/api/services/image.py | 72 +++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 27 deletions(-) create mode 100644 nova/api/rackspace/notes.txt create mode 100644 nova/api/services/__init__.py create mode 100644 nova/api/services/image.py (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 57c03894a..e29f737a5 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. -from nova.endpoint.rackspace.controllers.base import BaseController -from nova.endpoint import images +from nova import datastore +from nova.api.rackspace import base +from nova.api.services.image import ImageService from webob import exc #TODO(gundlach): Serialize return values -class Controller(BaseController): +class Controller(base.Controller): _serialization_metadata = { 'application/xml': { @@ -31,34 +32,62 @@ class Controller(BaseController): } } + def __init__(self): + self._svc = ImageService.load() + self._id_xlator = RackspaceApiImageIdTranslator() + + def _to_rs_id(self, image_id): + """ + Convert an image id from the format of our ImageService strategy + to the Rackspace API format (an int). + """ + strategy = self._svc.__class__.__name__ + return self._id_xlator.to_rs_id(strategy, image_id) + def index(self, req): - context = req.environ['nova.api_request_context'] - return images.list(context) + """Return all public images.""" + data = self._svc.list() + for img in data: + img['id'] = self._to_rs_id(img['id']) + return dict(images=result) def show(self, req, id): - context = req.environ['nova.api_request_context'] - return images.list(context, filter_list=[id]) + """Return data about the given image id.""" + img = self._svc.show(id) + img['id'] = self._to_rs_id(img['id']) + return dict(image=img) def delete(self, req, id): - context = req.environ['nova.api_request_context'] - # TODO(gundlach): make sure it's an image they may delete? - return images.deregister(context, 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() + + +class RackspaceApiImageIdTranslator(object): + """ + Converts Rackspace API image ids to and from the id format for a given + strategy. + """ - def create(self, **kwargs): - # TODO(gundlach): no idea how to hook this up. code below - # is from servers.py. - inst = self.build_server_instance(kwargs['server']) - rpc.cast( - FLAGS.compute_topic, { - "method": "run_instance", - "args": {"instance_id": inst.instance_id}}) + def __init__(self): + self._store = datastore.Redis.instance() - def update(self, **kwargs): - # TODO (gundlach): no idea how to hook this up. code below - # is from servers.py. - 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 to_rs_id(self, strategy_name, opaque_id): + """Convert an id from a strategy-specific one to a Rackspace one.""" + key = "rsapi.idstrategies.image.%s" % strategy_name + result = self._store.hget(key, str(opaque_id)) + if result: # we have a mapping from opaque to RS for this strategy + return int(result) + else: + nextid = self._store.incr("%s.lastid" % key) + self._store.hsetnx(key, str(opaque_id), nextid) + return nextid 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/services/__init__.py b/nova/api/services/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/services/image.py b/nova/api/services/image.py new file mode 100644 index 000000000..c5ea15ba1 --- /dev/null +++ b/nova/api/services/image.py @@ -0,0 +1,72 @@ +import cPickle as pickle +import os.path +import string + +class ImageService(object): + """Provides storage and retrieval of disk image objects.""" + + @staticmethod + def load(): + """Factory method to return image service.""" + #TODO(gundlach): read from config. + class_ = LocalImageService + return class_() + + def index(self): + """ + Return a list of image data dicts. Each dict will contain an + id key whose value is an opaque image id. + """ + + def show(self, id): + """ + Returns a dict containing image data for the given opaque image id. + """ + + +class GlanceImageService(ImageService): + """Provides storage and retrieval of disk image objects within Glance.""" + # TODO(gundlach): once Glance has an API, build this. + pass + + +class LocalImageService(ImageService): + """Image service storing images to local disk.""" + + def __init__(self): + self._path = "/tmp/nova/images" + try: + os.makedirs(self._path) + except OSError: # exists + pass + + def _path_to(self, image_id=''): + return os.path.join(self._path, image_id) + + def _ids(self): + """The list of all image ids.""" + return os.path.listdir(self._path) + + def index(self): + return [ self.show(id) for id in self._ids() ] + + def show(self, id): + return pickle.load(open(self._path_to(id))) + + def create(self, data): + """ + Store the image data and return the new image id. + """ + id = ''.join(random.choice(string.letters) for _ in range(20)) + self.update(id, data) + return id + + def update(self, image_id, data): + """Replace the contents of the given image with the new data.""" + pickle.dump(data, open(self._path_to(image_id), 'w')) + + def delete(self, image_id): + """ + Delete the given image. Raises OSError if the image does not exist. + """ + os.unlink(self._path_to(image_id)) -- cgit From d38f21e0fb382bd8f01cfbc79cb34ea8710cd639 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 10:27:59 -0400 Subject: License --- nova/api/services/image.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'nova/api') diff --git a/nova/api/services/image.py b/nova/api/services/image.py index c5ea15ba1..bda50fc66 100644 --- a/nova/api/services/image.py +++ b/nova/api/services/image.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 cPickle as pickle import os.path import string -- cgit From e3727d6d88a0631d3b896c4fcdcfec05510dad36 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 12:07:20 -0400 Subject: Support opaque id to rs int id as well --- nova/api/rackspace/images.py | 42 ++++++++++++++++++++++++++++++++---------- nova/api/services/image.py | 8 ++++---- 2 files changed, 36 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index e29f737a5..c9cc8e85d 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -44,18 +44,24 @@ class Controller(base.Controller): strategy = self._svc.__class__.__name__ return self._id_xlator.to_rs_id(strategy, image_id) + def _from_rs_id(self, rs_image_id): + """ + Convert an image id from the Rackspace API format (an int) to the + format of our ImageService strategy. + """ + strategy = self._svc.__class__.__name__ + return self._id_xlator.from_rs_id(strategy, rs_image_id) + def index(self, req): """Return all public images.""" - data = self._svc.list() - for img in data: - img['id'] = self._to_rs_id(img['id']) - return dict(images=result) + data = dict((self._to_rs_id(id), val) + for id, val in self._svc.index().iteritems()) + return dict(images=data) def show(self, req, id): """Return data about the given image id.""" - img = self._svc.show(id) - img['id'] = self._to_rs_id(img['id']) - return dict(image=img) + opaque_id = self._from_rs_id(id) + return dict(image=self._svc.show(opaque_id)) def delete(self, req, id): # Only public images are supported for now. @@ -80,14 +86,30 @@ class RackspaceApiImageIdTranslator(object): def __init__(self): self._store = datastore.Redis.instance() + self._key_template = "rsapi.idstrategies.image.%s.%s" def to_rs_id(self, strategy_name, opaque_id): """Convert an id from a strategy-specific one to a Rackspace one.""" - key = "rsapi.idstrategies.image.%s" % strategy_name + key = self._key_template % (strategy_name, "fwd") result = self._store.hget(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" % key) - self._store.hsetnx(key, str(opaque_id), nextid) - return nextid + if self._store.hsetnx(key, str(opaque_id), nextid): + # If someone else didn't beat us to it, store the reverse + # mapping as well. + key = self._key_template % (strategy_name, "rev") + self._store.hset(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(key, str(opaque_id))) + + def from_rs_id(self, strategy_name, rs_id): + """Convert a Rackspace id to a strategy-specific one.""" + key = self._key_template % (strategy_name, "rev") + return self._store.hget(key, rs_id) diff --git a/nova/api/services/image.py b/nova/api/services/image.py index bda50fc66..11e19804a 100644 --- a/nova/api/services/image.py +++ b/nova/api/services/image.py @@ -17,6 +17,7 @@ import cPickle as pickle import os.path +import random import string class ImageService(object): @@ -31,8 +32,7 @@ class ImageService(object): def index(self): """ - Return a list of image data dicts. Each dict will contain an - id key whose value is an opaque image id. + Return a dict from opaque image id to image data. """ def show(self, id): @@ -62,10 +62,10 @@ class LocalImageService(ImageService): def _ids(self): """The list of all image ids.""" - return os.path.listdir(self._path) + return os.listdir(self._path) def index(self): - return [ self.show(id) for id in self._ids() ] + return dict((id, self.show(id)) for id in self._ids()) def show(self, id): return pickle.load(open(self._path_to(id))) -- cgit From 030d01fd10f7f65cdafbea49e04f3b6b147a7348 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 12:46:29 -0400 Subject: Serialize properly --- nova/api/rackspace/base.py | 3 ++- nova/api/rackspace/images.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/base.py index c85fd7b8e..b995d9acc 100644 --- a/nova/api/rackspace/base.py +++ b/nova/api/rackspace/base.py @@ -36,4 +36,5 @@ class Controller(wsgi.Controller): MIME types to information needed to serialize to that type. """ _metadata = getattr(type(self), "_serialization_metadata", {}) - return Serializer(request.environ, _metadata).to_content_type(data) + serializer = wsgi.Serializer(request.environ, _metadata) + return serializer.to_content_type(data) diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index c9cc8e85d..62e0b24c5 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -56,12 +56,12 @@ class Controller(base.Controller): """Return all public images.""" data = dict((self._to_rs_id(id), val) for id, val in self._svc.index().iteritems()) - return dict(images=data) + return self.serialize(dict(images=data), req) def show(self, req, id): """Return data about the given image id.""" opaque_id = self._from_rs_id(id) - return dict(image=self._svc.show(opaque_id)) + return self.serialize(dict(image=self._svc.show(opaque_id)), req) def delete(self, req, id): # Only public images are supported for now. -- cgit From a50a200bc2547439a3da17e695224d3d434e14dd Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 12:55:57 -0400 Subject: Move serialize() to wsgi.Controller so __call__ can serialize() action return values if they are dicts. --- nova/api/rackspace/base.py | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/base.py index b995d9acc..51841925e 100644 --- a/nova/api/rackspace/base.py +++ b/nova/api/rackspace/base.py @@ -28,13 +28,3 @@ class Controller(wsgi.Controller): return {cls.entity_name: cls.render(instance)} else: return { "TODO": "TODO" } - - def serialize(self, data, request): - """ - Serialize the given dict to the response type requested in request. - Uses self._serialization_metadata if it exists, which is a dict mapping - MIME types to information needed to serialize to that type. - """ - _metadata = getattr(type(self), "_serialization_metadata", {}) - serializer = wsgi.Serializer(request.environ, _metadata) - return serializer.to_content_type(data) -- cgit From f5c03fdd78a3bb8233e465c7624ed1fdb8f400fe Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 13:06:40 -0400 Subject: Don't serialize in Controller subclass now that wsgi.Controller handles it for us --- nova/api/rackspace/images.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 62e0b24c5..070100143 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -20,7 +20,6 @@ from nova.api.rackspace import base from nova.api.services.image import ImageService from webob import exc -#TODO(gundlach): Serialize return values class Controller(base.Controller): _serialization_metadata = { @@ -56,12 +55,12 @@ class Controller(base.Controller): """Return all public images.""" data = dict((self._to_rs_id(id), val) for id, val in self._svc.index().iteritems()) - return self.serialize(dict(images=data), req) + return dict(images=data) def show(self, req, id): """Return data about the given image id.""" opaque_id = self._from_rs_id(id) - return self.serialize(dict(image=self._svc.show(opaque_id)), req) + return dict(image=self._svc.show(opaque_id)) def delete(self, req, id): # Only public images are supported for now. -- cgit From 35a08780c41ece1b47b2ded98c061b103a400fea Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 13:26:10 -0400 Subject: Get the output formatting correct. --- nova/api/rackspace/images.py | 9 ++++++--- nova/api/services/image.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 070100143..7d32fa099 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -53,14 +53,17 @@ class Controller(base.Controller): def index(self, req): """Return all public images.""" - data = dict((self._to_rs_id(id), val) - for id, val in self._svc.index().iteritems()) + data = self._svc.index() + for img in data: + img['id'] = self._to_rs_id(img['id']) return dict(images=data) def show(self, req, id): """Return data about the given image id.""" opaque_id = self._from_rs_id(id) - return dict(image=self._svc.show(opaque_id)) + img = self._svc.show(opaque_id) + img['id'] = id + return dict(image=img) def delete(self, req, id): # Only public images are supported for now. diff --git a/nova/api/services/image.py b/nova/api/services/image.py index 11e19804a..1a7a258b7 100644 --- a/nova/api/services/image.py +++ b/nova/api/services/image.py @@ -65,7 +65,7 @@ class LocalImageService(ImageService): return os.listdir(self._path) def index(self): - return dict((id, self.show(id)) for id in self._ids()) + return [ self.show(id) for id in self._ids() ] def show(self, id): return pickle.load(open(self._path_to(id))) @@ -75,6 +75,7 @@ class LocalImageService(ImageService): Store the image data and return the new image id. """ id = ''.join(random.choice(string.letters) for _ in range(20)) + data['id'] = id self.update(id, data) return id -- cgit From 41e2e91ccfb1409f1ea47d92a9d15f47ab37e65d Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Mon, 23 Aug 2010 16:43:25 -0400 Subject: Merge fail --- nova/api/rackspace/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/base.py b/nova/api/rackspace/base.py index 51841925e..dd2c6543c 100644 --- a/nova/api/rackspace/base.py +++ b/nova/api/rackspace/base.py @@ -27,4 +27,4 @@ class Controller(wsgi.Controller): if isinstance(instance, list): return {cls.entity_name: cls.render(instance)} else: - return { "TODO": "TODO" } + return {"TODO": "TODO"} -- cgit From d94eec3d2995c97c38006e4d6177740375860f8f Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 24 Aug 2010 11:19:51 -0400 Subject: Style fixes --- nova/api/rackspace/images.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 7d32fa099..36a26688c 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -17,7 +17,7 @@ from nova import datastore from nova.api.rackspace import base -from nova.api.services.image import ImageService +from nova.api.services import image from webob import exc class Controller(base.Controller): @@ -32,28 +32,28 @@ class Controller(base.Controller): } def __init__(self): - self._svc = ImageService.load() - self._id_xlator = RackspaceApiImageIdTranslator() + self._service = image.ImageService.load() + self._id_translator = RackspaceAPIImageIdTranslator() def _to_rs_id(self, image_id): """ Convert an image id from the format of our ImageService strategy to the Rackspace API format (an int). """ - strategy = self._svc.__class__.__name__ - return self._id_xlator.to_rs_id(strategy, image_id) + strategy = self._service.__class__.__name__ + return self._id_translator.to_rs_id(strategy, image_id) def _from_rs_id(self, rs_image_id): """ Convert an image id from the Rackspace API format (an int) to the format of our ImageService strategy. """ - strategy = self._svc.__class__.__name__ - return self._id_xlator.from_rs_id(strategy, rs_image_id) + strategy = self._service.__class__.__name__ + return self._id_translator.from_rs_id(strategy, rs_image_id) def index(self, req): """Return all public images.""" - data = self._svc.index() + data = self._service.index() for img in data: img['id'] = self._to_rs_id(img['id']) return dict(images=data) @@ -61,7 +61,7 @@ class Controller(base.Controller): def show(self, req, id): """Return data about the given image id.""" opaque_id = self._from_rs_id(id) - img = self._svc.show(opaque_id) + img = self._service.show(opaque_id) img['id'] = id return dict(image=img) @@ -80,7 +80,7 @@ class Controller(base.Controller): raise exc.HTTPNotFound() -class RackspaceApiImageIdTranslator(object): +class RackspaceAPIImageIdTranslator(object): """ Converts Rackspace API image ids to and from the id format for a given strategy. -- cgit From 09bc71460b976f28c7bc6a507006d6c7c12c5824 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 24 Aug 2010 16:16:41 -0400 Subject: Move imageservice to its own directory --- nova/api/rackspace/images.py | 4 +- nova/api/services/image.py | 90 -------------------------------------------- 2 files changed, 2 insertions(+), 92 deletions(-) delete mode 100644 nova/api/services/image.py (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 36a26688c..b7668a1e1 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -17,7 +17,7 @@ from nova import datastore from nova.api.rackspace import base -from nova.api.services import image +from nova import image from webob import exc class Controller(base.Controller): @@ -32,7 +32,7 @@ class Controller(base.Controller): } def __init__(self): - self._service = image.ImageService.load() + self._service = image.service.ImageService.load() self._id_translator = RackspaceAPIImageIdTranslator() def _to_rs_id(self, image_id): diff --git a/nova/api/services/image.py b/nova/api/services/image.py deleted file mode 100644 index 1a7a258b7..000000000 --- a/nova/api/services/image.py +++ /dev/null @@ -1,90 +0,0 @@ -# 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 cPickle as pickle -import os.path -import random -import string - -class ImageService(object): - """Provides storage and retrieval of disk image objects.""" - - @staticmethod - def load(): - """Factory method to return image service.""" - #TODO(gundlach): read from config. - class_ = LocalImageService - return class_() - - def index(self): - """ - Return a dict from opaque image id to image data. - """ - - def show(self, id): - """ - Returns a dict containing image data for the given opaque image id. - """ - - -class GlanceImageService(ImageService): - """Provides storage and retrieval of disk image objects within Glance.""" - # TODO(gundlach): once Glance has an API, build this. - pass - - -class LocalImageService(ImageService): - """Image service storing images to local disk.""" - - def __init__(self): - self._path = "/tmp/nova/images" - try: - os.makedirs(self._path) - except OSError: # exists - pass - - def _path_to(self, image_id=''): - return os.path.join(self._path, image_id) - - def _ids(self): - """The list of all image ids.""" - return os.listdir(self._path) - - def index(self): - return [ self.show(id) for id in self._ids() ] - - def show(self, id): - return pickle.load(open(self._path_to(id))) - - def create(self, data): - """ - Store the image data and return the new image id. - """ - id = ''.join(random.choice(string.letters) for _ in range(20)) - data['id'] = id - self.update(id, data) - return id - - def update(self, image_id, data): - """Replace the contents of the given image with the new data.""" - pickle.dump(data, open(self._path_to(image_id), 'w')) - - def delete(self, image_id): - """ - Delete the given image. Raises OSError if the image does not exist. - """ - os.unlink(self._path_to(image_id)) -- cgit From 5f832cd5ea9fb858f5e8b09992cbd47d8d16f665 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 24 Aug 2010 16:17:06 -0400 Subject: Delete unused directory --- nova/api/services/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 nova/api/services/__init__.py (limited to 'nova/api') diff --git a/nova/api/services/__init__.py b/nova/api/services/__init__.py deleted file mode 100644 index e69de29bb..000000000 -- cgit From 96ae5e2776218adfee2cb22a4c0d7358a498a451 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 24 Aug 2010 16:24:24 -0400 Subject: pep8 --- nova/api/rackspace/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index b7668a1e1..370980fe9 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -16,8 +16,8 @@ # under the License. from nova import datastore -from nova.api.rackspace import base from nova import image +from nova.api.rackspace import base from webob import exc class Controller(base.Controller): -- cgit From 3b70003d932607ccc13fe4cd9381475035603a70 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 24 Aug 2010 17:03:03 -0400 Subject: Flavors work --- nova/api/rackspace/flavors.py | 34 +++++++++++++++++++++++- nova/api/rackspace/images.py | 62 ++++--------------------------------------- 2 files changed, 38 insertions(+), 58 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 986f11434..8c5ffa438 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -15,4 +15,36 @@ # License for the specific language governing permissions and limitations # under the License. -class Controller(object): pass +from nova.api.rackspace import base +from nova.api.rackspace import _id_translator +from nova import flavor +from webob import exc + +class Controller(base.Controller): + + _serialization_metadata = { + 'application/xml': { + "attributes": { + "flavor": [ "id", "name", "ram", "disk" ] + } + } + } + + def __init__(self): + self._service = flavor.service.FlavorService.load() + self._id_translator = self._id_translator.RackspaceAPIIdTranslator( + "flavor", self._service.__class__.__name__) + + def index(self, req): + """Return all flavors.""" + items = self._service.index() + for flavor in items: + flavor['id'] = self._id_translator.to_rs_id(flavor['id']) + return dict(flavors=items) + + def show(self, req, id): + """Return data about the given flavor id.""" + opaque_id = self._id_translator.from_rs_id(id) + item = self._service.show(opaque_id) + item['id'] = id + return dict(flavor=item) diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 36a26688c..09f55ea96 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -15,8 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. -from nova import datastore from nova.api.rackspace import base +from nova.api.rackspace import _id_translator from nova.api.services import image from webob import exc @@ -33,34 +33,19 @@ class Controller(base.Controller): def __init__(self): self._service = image.ImageService.load() - self._id_translator = RackspaceAPIImageIdTranslator() - - def _to_rs_id(self, image_id): - """ - Convert an image id from the format of our ImageService strategy - to the Rackspace API format (an int). - """ - strategy = self._service.__class__.__name__ - return self._id_translator.to_rs_id(strategy, image_id) - - def _from_rs_id(self, rs_image_id): - """ - Convert an image id from the Rackspace API format (an int) to the - format of our ImageService strategy. - """ - strategy = self._service.__class__.__name__ - return self._id_translator.from_rs_id(strategy, rs_image_id) + self._id_translator = _id_translator.RackspaceAPIIdTranslator( + "image", self._service.__class__.__name__) def index(self, req): """Return all public images.""" data = self._service.index() for img in data: - img['id'] = self._to_rs_id(img['id']) + 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._from_rs_id(id) + opaque_id = self._id_translator.from_rs_id(id) img = self._service.show(opaque_id) img['id'] = id return dict(image=img) @@ -78,40 +63,3 @@ class Controller(base.Controller): # Users may not modify public images, and that's all that # we support for now. raise exc.HTTPNotFound() - - -class RackspaceAPIImageIdTranslator(object): - """ - Converts Rackspace API image ids to and from the id format for a given - strategy. - """ - - def __init__(self): - self._store = datastore.Redis.instance() - self._key_template = "rsapi.idstrategies.image.%s.%s" - - def to_rs_id(self, strategy_name, opaque_id): - """Convert an id from a strategy-specific one to a Rackspace one.""" - key = self._key_template % (strategy_name, "fwd") - result = self._store.hget(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" % key) - if self._store.hsetnx(key, str(opaque_id), nextid): - # If someone else didn't beat us to it, store the reverse - # mapping as well. - key = self._key_template % (strategy_name, "rev") - self._store.hset(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(key, str(opaque_id))) - - def from_rs_id(self, strategy_name, rs_id): - """Convert a Rackspace id to a strategy-specific one.""" - key = self._key_template % (strategy_name, "rev") - return self._store.hget(key, rs_id) -- cgit From 0828326898e3bc219c8205e27a3cc942e2790934 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 25 Aug 2010 16:27:01 -0400 Subject: Use compute.instance_types for flavor data instead of a FlavorService --- nova/api/rackspace/flavors.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 8c5ffa438..59981f1c5 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -16,11 +16,11 @@ # under the License. from nova.api.rackspace import base -from nova.api.rackspace import _id_translator -from nova import flavor +from nova.compute import instance_types from webob import exc class Controller(base.Controller): + """Flavor controller for the Rackspace API.""" _serialization_metadata = { 'application/xml': { @@ -30,21 +30,20 @@ class Controller(base.Controller): } } - def __init__(self): - self._service = flavor.service.FlavorService.load() - self._id_translator = self._id_translator.RackspaceAPIIdTranslator( - "flavor", self._service.__class__.__name__) - def index(self, req): """Return all flavors.""" - items = self._service.index() - for flavor in items: - flavor['id'] = self._id_translator.to_rs_id(flavor['id']) + 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.""" - opaque_id = self._id_translator.from_rs_id(id) - item = self._service.show(opaque_id) - item['id'] = id - return dict(flavor=item) + 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()] -- cgit From cf0b5de1f78fd81ada2bada8c84e26b3238b8596 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 25 Aug 2010 16:46:53 -0400 Subject: Turn imageid translator into general translator for rackspace api ids --- nova/api/rackspace/_id_translator.py | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 nova/api/rackspace/_id_translator.py (limited to 'nova/api') 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) -- cgit From c9d3b7c3ae71bbbe6f3077dcee13be41a14a6733 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 25 Aug 2010 17:48:08 -0400 Subject: Support GET //detail --- nova/api/rackspace/__init__.py | 6 ++++-- nova/api/rackspace/flavors.py | 7 ++++++- nova/api/rackspace/images.py | 11 ++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 27e78f801..b4d666d63 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -74,8 +74,10 @@ class APIRouter(wsgi.Router): def __init__(self): mapper = routes.Mapper() mapper.resource("server", "servers", controller=servers.Controller()) - mapper.resource("image", "images", controller=images.Controller()) - mapper.resource("flavor", "flavors", controller=flavors.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/flavors.py b/nova/api/rackspace/flavors.py index 59981f1c5..60b35c939 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -31,7 +31,12 @@ class Controller(base.Controller): } def index(self, req): - """Return all flavors.""" + """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) diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 06fb0d38f..2f3e928b9 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -from nova import image +import nova.image.service from nova.api.rackspace import base from nova.api.rackspace import _id_translator from webob import exc @@ -32,12 +32,17 @@ class Controller(base.Controller): } def __init__(self): - self._service = image.ImageService.load() + 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.""" + """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']) -- cgit