From 942762d48afc81f1f71652b0861953e32c78b124 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 15 Feb 2012 15:37:57 -0800 Subject: [PATCH] Add additional information to servers output. * Improves cloud debugability, by letting admins see on which host a server has been launched * Addresses blueprint optional-host-and-admin-information * Add test * Rebase to master, fix tests accordingly Change-Id: Iba173ca3b1ead716e274f1d287cebbc7961d8a93 --- etc/nova/policy.json | 1 + .../contrib/extended_server_attributes.py | 129 ++++++++++++++++++ .../test_extended_server_attributes.py | 95 +++++++++++++ .../api/openstack/compute/test_extensions.py | 1 + nova/tests/policy.json | 1 + 5 files changed, 227 insertions(+) create mode 100644 nova/api/openstack/compute/contrib/extended_server_attributes.py create mode 100644 nova/tests/api/openstack/compute/contrib/test_extended_server_attributes.py diff --git a/etc/nova/policy.json b/etc/nova/policy.json index f0457fd9f473..da62eb4460f3 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -31,6 +31,7 @@ "compute_extension:createserverext": [], "compute_extension:deferred_delete": [], "compute_extension:disk_config": [], + "compute_extension:extended_server_attributes": [["rule:admin_api"]], "compute_extension:extended_status": [["rule:admin_api"]], "compute_extension:flavorextraspecs": [], "compute_extension:flavormanage": [["rule:admin_api"]], diff --git a/nova/api/openstack/compute/contrib/extended_server_attributes.py b/nova/api/openstack/compute/contrib/extended_server_attributes.py new file mode 100644 index 000000000000..102477db3a4e --- /dev/null +++ b/nova/api/openstack/compute/contrib/extended_server_attributes.py @@ -0,0 +1,129 @@ +# Copyright 2012 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 Server Attributes API extension.""" + +from webob import exc + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import compute +from nova import exception +from nova import flags +from nova import log as logging + + +FLAGS = flags.FLAGS +LOG = logging.getLogger("nova.api.openstack.compute.contrib." + "extended_server_attributes") +authorize = extensions.soft_extension_authorizer('compute', + 'extended_server_attributes') + + +class ExtendedServerAttributesController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(ExtendedServerAttributesController, self).__init__(*args, + **kwargs) + self.compute_api = compute.API() + + def _get_instances(self, context, instance_uuids): + filters = {'uuid': instance_uuids} + instances = self.compute_api.get_all(context, filters) + return dict((instance['uuid'], instance) for instance in instances) + + def _extend_server(self, server, instance): + for attr in ['host', 'name']: + if attr == 'name': + key = "%s:instance_%s" % (Extended_server_attributes.alias, + attr) + else: + key = "%s:%s" % (Extended_server_attributes.alias, attr) + server[key] = instance[attr] + + @wsgi.extends + def show(self, req, resp_obj, id): + context = req.environ['nova.context'] + if authorize(context): + # Attach our slave template to the response object + resp_obj.attach(xml=ExtendedServerAttributesTemplate()) + + try: + instance = self.compute_api.get(context, id) + except exception.NotFound: + explanation = _("Server not found.") + raise exc.HTTPNotFound(explanation=explanation) + + self._extend_server(resp_obj.obj['server'], instance) + + @wsgi.extends + def detail(self, req, resp_obj): + context = req.environ['nova.context'] + if authorize(context): + # Attach our slave template to the response object + resp_obj.attach(xml=ExtendedServerAttributesTemplate()) + + servers = list(resp_obj.obj['servers']) + instance_uuids = [server['id'] for server in servers] + instances = self._get_instances(context, instance_uuids) + + for server_object in servers: + try: + instance_data = instances[server_object['id']] + except KeyError: + # Ignore missing instance data + continue + + self._extend_server(server_object, instance_data) + + +class Extended_server_attributes(extensions.ExtensionDescriptor): + """Extended Server Attributes support.""" + + name = "ExtendedServerAttributes" + alias = "OS-EXT-SRV-ATTR" + namespace = "http://docs.openstack.org/compute/ext/" \ + "extended_status/api/v1.1" + updated = "2011-11-03T00:00:00+00:00" + + def get_controller_extensions(self): + controller = ExtendedServerAttributesController() + extension = extensions.ControllerExtension(self, 'servers', controller) + return [extension] + + +def make_server(elem): + elem.set('{%s}instance_name' % Extended_server_attributes.namespace, + '%s:instance_name' % Extended_server_attributes.alias) + elem.set('{%s}host' % Extended_server_attributes.namespace, + '%s:host' % Extended_server_attributes.alias) + + +class ExtendedServerAttributeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('server', selector='server') + make_server(root) + return xmlutil.SlaveTemplate(root, 1, nsmap={ + Extended_server_attributes.alias: \ + Extended_server_attributes.namespace}) + + +class ExtendedServerAttributesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('servers') + elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') + make_server(elem) + return xmlutil.SlaveTemplate(root, 1, nsmap={ + Extended_server_attributes.alias: \ + Extended_server_attributes.namespace}) diff --git a/nova/tests/api/openstack/compute/contrib/test_extended_server_attributes.py b/nova/tests/api/openstack/compute/contrib/test_extended_server_attributes.py new file mode 100644 index 000000000000..9944fc0e8f3f --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_extended_server_attributes.py @@ -0,0 +1,95 @@ +# 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 webob + +from nova import compute +from nova import exception +from nova import flags +from nova import test +from nova.tests.api.openstack import fakes + + +FLAGS = flags.FLAGS + + +UUID1 = '00000000-0000-0000-0000-000000000001' +UUID2 = '00000000-0000-0000-0000-000000000002' +UUID3 = '00000000-0000-0000-0000-000000000003' + + +def fake_compute_get(*args, **kwargs): + return fakes.stub_instance(1, uuid=UUID3, host="host-fake") + + +def fake_compute_get_all(*args, **kwargs): + return [ + fakes.stub_instance(1, uuid=UUID1, host="host-1"), + fakes.stub_instance(2, uuid=UUID2, host="host-2") + ] + + +class ExtendedServerAttributesTest(test.TestCase): + + def setUp(self): + super(ExtendedServerAttributesTest, self).setUp() + fakes.stub_out_nw_api(self.stubs) + self.stubs.Set(compute.api.API, 'get', fake_compute_get) + self.stubs.Set(compute.api.API, 'get_all', fake_compute_get_all) + + def _make_request(self, url): + req = webob.Request.blank(url) + req.headers['Accept'] = 'application/json' + res = req.get_response(fakes.wsgi_app()) + return res + + def assertServerAttributes(self, server, host, instance_name): + self.assertEqual(server.get('OS-EXT-SRV-ATTR:host'), host) + self.assertEqual(server.get('OS-EXT-SRV-ATTR:instance_name'), + instance_name) + + def test_show(self): + url = '/v2/fake/servers/%s' % UUID3 + res = self._make_request(url) + body = json.loads(res.body) + + self.assertEqual(res.status_int, 200) + self.assertServerAttributes(body['server'], + host='host-fake', + instance_name='instance-1') + + def test_detail(self): + url = '/v2/fake/servers/detail' + res = self._make_request(url) + body = json.loads(res.body) + + self.assertEqual(res.status_int, 200) + for i, server in enumerate(body['servers']): + self.assertServerAttributes(server, + host='host-%s' % (i + 1), + instance_name='instance-%s' % (i + 1)) + + def test_no_instance_passthrough_404(self): + + def fake_compute_get(*args, **kwargs): + raise exception.InstanceNotFound() + + self.stubs.Set(compute.api.API, 'get', fake_compute_get) + url = '/v2/fake/servers/70f6db34-de8d-4fbd-aafb-4065bdfa6115' + res = self._make_request(url) + + self.assertEqual(res.status_int, 404) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index 1a281590f873..5140472c6d47 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -161,6 +161,7 @@ class ExtensionControllerTest(ExtensionTestCase): "DeferredDelete", "DiskConfig", "ExtendedStatus", + "ExtendedServerAttributes", "FlavorExtraSpecs", "FlavorExtraData", "FlavorManage", diff --git a/nova/tests/policy.json b/nova/tests/policy.json index 99a97bba5f31..7c6fb0428d34 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -88,6 +88,7 @@ "compute_extension:createserverext": [], "compute_extension:deferred_delete": [], "compute_extension:disk_config": [], + "compute_extension:extended_server_attributes": [], "compute_extension:extended_status": [], "compute_extension:flavorextraspecs": [], "compute_extension:flavormanage": [],