diff options
| author | Dan Prince <dan.prince@rackspace.com> | 2011-03-18 02:14:36 -0400 |
|---|---|---|
| committer | Dan Prince <dan.prince@rackspace.com> | 2011-03-18 02:14:36 -0400 |
| commit | 1abcdbea89e69013c193d2eb0b4b7a0bc2e2fa58 (patch) | |
| tree | 6656e0ba194ab5de87d1dfdc9435e46af883c23c | |
| parent | 79f2f90feec74b97d55af058c9bec4177bc47a54 (diff) | |
| download | nova-1abcdbea89e69013c193d2eb0b4b7a0bc2e2fa58.tar.gz nova-1abcdbea89e69013c193d2eb0b4b7a0bc2e2fa58.tar.xz nova-1abcdbea89e69013c193d2eb0b4b7a0bc2e2fa58.zip | |
Implement metadata resource for Openstack API v1.1. Includes:
-GET /servers/id/meta
-POST /servers/id/meta
-GET /servers/id/meta/key
-PUT /servers/id/meta/key
-DELETE /servers/id/meta/key
| -rw-r--r-- | nova/api/openstack/__init__.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/server_metadata.py | 75 | ||||
| -rw-r--r-- | nova/compute/api.py | 15 | ||||
| -rw-r--r-- | nova/db/api.py | 18 | ||||
| -rw-r--r-- | nova/db/sqlalchemy/api.py | 60 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_server_metadata.py | 150 |
6 files changed, 323 insertions, 0 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0b50d17d0..4beb84dcd 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -34,6 +34,7 @@ from nova.api.openstack import consoles from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import servers +from nova.api.openstack import server_metadata from nova.api.openstack import shared_ip_groups from nova.api.openstack import users from nova.api.openstack import zones @@ -138,6 +139,10 @@ class APIRouterV11(APIRouter): controller=servers.ControllerV11(), collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("server_meta", "meta", + controller=server_metadata.Controller(), + parent_resource=dict(member_name='server', + collection_name='servers')) class Versions(wsgi.Application): diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py new file mode 100644 index 000000000..1408f59a6 --- /dev/null +++ b/nova/api/openstack/server_metadata.py @@ -0,0 +1,75 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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 webob import exc + +from nova import compute +from nova import wsgi +from nova.api.openstack import faults + + +class Controller(wsgi.Controller): + """ The server metadata API controller for the Openstack API """ + + def __init__(self): + self.compute_api = compute.API() + super(Controller, self).__init__() + + def _get_metadata(self, context, server_id): + metadata = self.compute_api.get_instance_metadata(context, server_id) + meta_dict = {} + for key, value in metadata.iteritems(): + meta_dict[key] = value + return dict(metadata=meta_dict) + + def index(self, req, server_id): + """ Returns the list of metadata for a given instance """ + context = req.environ['nova.context'] + return self._get_metadata(context, server_id) + + def create(self, req, server_id): + context = req.environ['nova.context'] + body = self._deserialize(req.body, req.get_content_type()) + self.compute_api.update_or_create_instance_metadata(context, + server_id, + body['metadata']) + return req.body + + def update(self, req, server_id, id): + context = req.environ['nova.context'] + body = self._deserialize(req.body, req.get_content_type()) + if not id in body: + expl = _('Request body and URI mismatch') + raise exc.HTTPBadRequest(explanation=expl) + self.compute_api.update_or_create_instance_metadata(context, + server_id, + body) + return req.body + + def show(self, req, server_id, id): + """ Return a single metadata item """ + context = req.environ['nova.context'] + data = self._get_metadata(context, server_id) + if id in data['metadata']: + return {id: data['metadata'][id]} + else: + return faults.Fault(exc.HTTPNotFound()) + + def delete(self, req, server_id, id): + """ Deletes an existing metadata """ + context = req.environ['nova.context'] + self.compute_api.delete_instance_metadata(context, server_id, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 32577af82..e70817212 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -612,3 +612,18 @@ class API(base.Base): instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, address, instance['fixed_ip']) + + def get_instance_metadata(self, context, instance_id): + """Get all metadata associated with an instance.""" + rv = self.db.get_instance_metadata(context, instance_id) + return dict(rv.iteritems()) + + def delete_instance_metadata(self, context, instance_id, key): + """Delete the given metadata item""" + self.db.delete_instance_metadata(context, instance_id, key) + + def update_or_create_instance_metadata(self, context, instance_id, + metadata): + """Updates or creates instance metadata""" + self.db.update_or_create_instance_metadata(context, instance_id, + metadata) diff --git a/nova/db/api.py b/nova/db/api.py index 3cb0e5811..5721fe8d6 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1171,3 +1171,21 @@ def zone_get(context, zone_id): def zone_get_all(context): """Get all child Zones.""" return IMPL.zone_get_all(context) + + +#################### + + +def get_instance_metadata(context, instance_id): + """Get all metadata for an instance""" + return IMPL.get_instance_metadata(context, instance_id) + + +def delete_instance_metadata(context, instance_id, key): + """Delete the given metadata item""" + IMPL.delete_instance_metadata(context, instance_id, key) + + +def update_or_create_instance_metadata(context, instance_id, metadata): + """Creates or updates instance metadata""" + IMPL.update_or_create_instance_metadata(context, instance_id, metadata) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9d9b86c1d..8f656de0e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2457,3 +2457,63 @@ def zone_get(context, zone_id): def zone_get_all(context): session = get_session() return session.query(models.Zone).all() + + +#################### + +@require_context +def get_instance_metadata(context, instance_id): + session = get_session() + + meta_results = session.query(models.InstanceMetadata).\ + filter_by(instance_id=instance_id).\ + filter_by(deleted=False).\ + all() + + meta_dict = {} + for i in meta_results: + meta_dict[i['key']] = i['value'] + return meta_dict + + +@require_context +def delete_instance_metadata(context, instance_id, key): + session = get_session() + session.query(models.InstanceMetadata).\ + filter_by(instance_id=instance_id).\ + filter_by(key=key).\ + update({'deleted': 1, + 'deleted_at': datetime.datetime.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_context +def get_instance_metadata_item(context, instance_id, key): + session = get_session() + + meta_result = session.query(models.InstanceMetadata).\ + filter_by(instance_id=instance_id).\ + filter_by(key=key).\ + filter_by(deleted=False).\ + first() + + if not meta_result: + raise exception.NotFound(_('Invalid metadata key for instance %s') % + instance_id) + return meta_result + + +@require_context +def update_or_create_instance_metadata(context, instance_id, metadata): + session = get_session() + meta_ref = None + for key, value in metadata.iteritems(): + try: + meta_ref = get_instance_metadata_item(context, instance_id, key, + session) + except: + meta_ref = models.InstanceMetadata() + meta_ref.update({"key": key, "value": value, + "instance_id": instance_id}) + meta_ref.save(session=session) + return metadata diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py new file mode 100644 index 000000000..b280ae94c --- /dev/null +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -0,0 +1,150 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 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 json +import stubout +import unittest +import webob + + +from nova.api import openstack +from nova.tests.api.openstack import fakes +import nova.wsgi + + +def return_create_instance_metadata(context, server_id, metadata): + return stub_server_metadata() + + +def return_server_metadata(context, server_id): + return stub_server_metadata() + + +def return_empty_server_metadata(context, server_id): + return {} + + +def delete_server_metadata(context, server_id, key): + pass + + +def stub_server_metadata(): + metadata = { + "key1": "value1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5" + } + return metadata + + +class ServerMetaDataTest(unittest.TestCase): + + def setUp(self): + super(ServerMetaDataTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + fakes.stub_out_key_pair_funcs(self.stubs) + #self.allow_admin = FLAGS.allow_admin_api + + def test_index(self): + self.stubs.Set(nova.db.api, 'get_instance_metadata', + return_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value1', res_dict['metadata']['key1']) + + def test_index_no_data(self): + self.stubs.Set(nova.db.api, 'get_instance_metadata', + return_empty_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual(0, len(res_dict['metadata'])) + + def test_show(self): + self.stubs.Set(nova.db.api, 'get_instance_metadata', + return_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/key5') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value5', res_dict['key5']) + + def test_show_meta_not_found(self): + self.stubs.Set(nova.db.api, 'get_instance_metadata', + return_empty_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/key6') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(404, res.status_int) + + def test_delete(self): + self.stubs.Set(nova.db.api, 'delete_instance_metadata', + delete_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/key5') + req.environ['api.version'] = '1.1' + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + + def test_create(self): + self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = '{"metadata": {"key1": "value1"}}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value1', res_dict['metadata']['key1']) + + def test_update_item(self): + self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual('value1', res_dict['key1']) + + def test_update_item_body_uri_mismatch(self): + self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/bad') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) |
