diff options
author | Matt Dietz <matt.dietz@rackspace.com> | 2011-11-08 14:12:46 -0600 |
---|---|---|
committer | Matt Dietz <matt.dietz@rackspace.com> | 2011-11-08 14:13:21 -0600 |
commit | 0973f558561173bdc84d7f4f8eb33da9fedafac2 (patch) | |
tree | d91112fcffa384bb925e0e7d34389bafae77cbaf | |
parent | 1c389d0bd8e9c9f21f760f9b8f5b79baffd38d39 (diff) | |
download | nova-0973f558561173bdc84d7f4f8eb33da9fedafac2.tar.gz nova-0973f558561173bdc84d7f4f8eb33da9fedafac2.tar.xz nova-0973f558561173bdc84d7f4f8eb33da9fedafac2.zip |
Adds extended status information via the Admin API to the servers calls
Change-Id: I294e64e878f1f6ebf33b83198abb2cdd8b6c5185
-rw-r--r-- | nova/api/openstack/contrib/extended_status.py | 110 | ||||
-rw-r--r-- | nova/api/openstack/wsgi.py | 2 | ||||
-rw-r--r-- | nova/tests/api/openstack/contrib/test_extendedstatus.py | 109 | ||||
-rw-r--r-- | nova/tests/api/openstack/test_extensions.py | 1 |
4 files changed, 221 insertions, 1 deletions
diff --git a/nova/api/openstack/contrib/extended_status.py b/nova/api/openstack/contrib/extended_status.py new file mode 100644 index 000000000..625f3ab2b --- /dev/null +++ b/nova/api/openstack/contrib/extended_status.py @@ -0,0 +1,110 @@ +# Copyright 2011 Openstack, LLC. +# +# 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. + +"""The Extended Status Admin API extension.""" + +import traceback + +import webob +from webob import exc + +from nova import compute +from nova import exception +from nova import flags +from nova import log as logging +from nova.api.openstack import extensions +from nova.api.openstack import faults +from nova.api.openstack import xmlutil + + +FLAGS = flags.FLAGS +LOG = logging.getLogger("nova.api.openstack.contrib.extendedstatus") + + +class Extended_status(extensions.ExtensionDescriptor): + """Extended Status support""" + + name = "ExtendedStatus" + alias = "OS-EXT-STS" + namespace = "http://docs.openstack.org/ext/extended_status/api/v1.1" + updated = "2011-11-03T00:00:00+00:00" + + def get_request_extensions(self): + request_extensions = [] + + def _get_and_extend_one(context, server_id, body): + compute_api = compute.API() + try: + inst_ref = compute_api.routing_get(context, server_id) + except exception.NotFound: + explanation = _("Server not found.") + raise exc.HTTPNotFound(explanation=explanation) + + for state in ['task_state', 'vm_state', 'power_state']: + key = "%s:%s" % (Extended_status.alias, state) + body['server'][key] = inst_ref[state] + + def _get_and_extend_all(context, body): + # TODO(mdietz): This is a brilliant argument for this to *not* + # be an extension. The problem is we either have to 1) duplicate + # the logic from the servers controller or 2) do what we did + # and iterate over the list of potentially sorted, limited + # and whatever else elements and find each individual. + compute_api = compute.API() + + for server in body['servers']: + try: + inst_ref = compute_api.routing_get(context, server['id']) + except exception.NotFound: + explanation = _("Server not found.") + raise exc.HTTPNotFound(explanation=explanation) + + for state in ['task_state', 'vm_state', 'power_state']: + key = "%s:%s" % (Extended_status.alias, state) + server[key] = inst_ref[state] + + def _extended_status_handler(req, res, body): + context = req.environ['nova.context'] + server_id = req.environ['wsgiorg.routing_args'][1].get('id') + + if 'nova.template' in req.environ: + tmpl = req.environ['nova.template'] + tmpl.attach(ExtendedStatusTemplate()) + + if server_id: + _get_and_extend_one(context, server_id, body) + else: + _get_and_extend_all(context, body) + return res + + if FLAGS.allow_admin_api: + req_ext = extensions.RequestExtension('GET', + '/:(project_id)/servers/:(id)', + _extended_status_handler) + request_extensions.append(req_ext) + + return request_extensions + + +class ExtendedStatusTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('server') + root.set('{%s}task_state' % Extended_status.namespace, + '%s:task_state' % Extended_status.alias) + root.set('{%s}power_state' % Extended_status.namespace, + '%s:power_state' % Extended_status.alias) + root.set('{%s}vm_state' % Extended_status.namespace, + '%s:vm_state' % Extended_status.alias) + return xmlutil.SlaveTemplate(root, 1, nsmap={ + Extended_status.alias: Extended_status.namespace}) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index f96a12eab..7deba6382 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -477,6 +477,7 @@ class ResponseSerializer(object): request.environ['nova.action'] = action if (hasattr(serializer, 'get_template') and 'nova.template' not in request.environ): + template = serializer.get_template(action) request.environ['nova.template'] = template else: @@ -512,7 +513,6 @@ class LazySerializationMiddleware(wsgi.Middleware): # Re-serialize the body response.body = serializer.serialize(utils.loads(response.body), **kwargs) - return response diff --git a/nova/tests/api/openstack/contrib/test_extendedstatus.py b/nova/tests/api/openstack/contrib/test_extendedstatus.py new file mode 100644 index 000000000..8dafd9c7d --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_extendedstatus.py @@ -0,0 +1,109 @@ +# 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 datetime +import json +import webob + +from nova import compute +from nova import exception +from nova import flags +from nova import image +from nova import test +from nova.tests.api.openstack import fakes + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + +FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + +FAKE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), + ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '10.0.2.12')] + +DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), + ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12')] + +INVALID_NETWORKS = [('invalid', 'invalid-ip-address')] + +INSTANCE = { + "id": 1, + "name": "fake", + "display_name": "test_server", + "uuid": FAKE_UUID, + "user_id": 'fake_user_id', + "task_state": "kayaking", + "vm_state": "slightly crunchy", + "power_state": "empowered", + "tenant_id": 'fake_tenant_id', + "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), + "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), + "security_groups": [{"id": 1, "name": "test"}], + "progress": 0, + "image_ref": 'http://foo.com/123', + "fixed_ips": [], + "instance_type": {"flavorid": '124'}, + } + + +class ExtendedStatusTest(test.TestCase): + + def setUp(self): + super(ExtendedStatusTest, self).setUp() + self.uuid = '70f6db34-de8d-4fbd-aafb-4065bdfa6114' + self.url = '/v1.1/openstack/servers/%s' % self.uuid + fakes.stub_out_nw_api(self.stubs) + + def test_extended_status_with_admin(self): + def fake_compute_get(*args, **kwargs): + return INSTANCE + + self.flags(allow_admin_api=True) + self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get) + req = webob.Request.blank(self.url) + req.headers['Accept'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + body = json.loads(res.body) + self.assertEqual(body['server']['OS-EXT-STS:vm_state'], + 'slightly crunchy') + self.assertEqual(body['server']['OS-EXT-STS:power_state'], 'empowered') + self.assertEqual(body['server']['OS-EXT-STS:task_state'], 'kayaking') + + def test_extended_status_no_admin(self): + def fake_compute_get(*args, **kwargs): + return INSTANCE + + self.flags(allow_admin_api=False) + self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get) + req = webob.Request.blank(self.url) + req.headers['Accept'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + body = json.loads(res.body) + self.assertEqual(body['server'].get('OS-EXT-STS:vm_state'), None) + self.assertEqual(body['server'].get('OS-EXT-STS:power_state'), None) + self.assertEqual(body['server'].get('OS-EXT-STS:task_state'), None) + + def test_extended_status_no_instance_fails(self): + def fake_compute_get(*args, **kwargs): + raise exception.InstanceNotFound() + + self.flags(allow_admin_api=True) + self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get) + req = webob.Request.blank(self.url) + req.headers['Accept'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index cb44288c7..b4fe3e730 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -102,6 +102,7 @@ class ExtensionControllerTest(ExtensionTestCase): "Createserverext", "DeferredDelete", "DiskConfig", + "ExtendedStatus", "FlavorExtraSpecs", "FlavorExtraData", "Floating_ips", |