summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatt Dietz <matt.dietz@rackspace.com>2011-11-08 14:12:46 -0600
committerMatt Dietz <matt.dietz@rackspace.com>2011-11-08 14:13:21 -0600
commit0973f558561173bdc84d7f4f8eb33da9fedafac2 (patch)
treed91112fcffa384bb925e0e7d34389bafae77cbaf
parent1c389d0bd8e9c9f21f760f9b8f5b79baffd38d39 (diff)
downloadnova-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.py110
-rw-r--r--nova/api/openstack/wsgi.py2
-rw-r--r--nova/tests/api/openstack/contrib/test_extendedstatus.py109
-rw-r--r--nova/tests/api/openstack/test_extensions.py1
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",