From c6ea206bf81830ce949f33bde928226435c99f4b Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 15 Dec 2011 00:55:00 -0500 Subject: Creating mechanism that loads Admin API extensions Related to blueprint separate-nova-adminapi Change-Id: I2491267e370f053b5ae4e844aba1d82c3b0f0d86 --- nova/api/openstack/v2/contrib/admin_actions.py | 13 +- nova/api/openstack/v2/contrib/extended_status.py | 15 +- nova/api/openstack/v2/contrib/hosts.py | 7 +- .../openstack/v2/contrib/simple_tenant_usage.py | 8 +- nova/api/openstack/v2/extensions.py | 33 ++-- .../api/openstack/v2/contrib/test_admin_actions.py | 20 +-- .../openstack/v2/contrib/test_extendedstatus.py | 16 +- nova/tests/api/openstack/v2/contrib/test_hosts.py | 172 +++++++++++++++++++++ .../api/openstack/v2/extensions/foxinsocks.py | 2 +- nova/tests/api/openstack/v2/test_extensions.py | 32 ++++ nova/tests/test_hosts.py | 172 --------------------- 11 files changed, 241 insertions(+), 249 deletions(-) create mode 100644 nova/tests/api/openstack/v2/contrib/test_hosts.py delete mode 100644 nova/tests/test_hosts.py diff --git a/nova/api/openstack/v2/contrib/admin_actions.py b/nova/api/openstack/v2/contrib/admin_actions.py index 632a8dc0e..46a4ec6f0 100644 --- a/nova/api/openstack/v2/contrib/admin_actions.py +++ b/nova/api/openstack/v2/contrib/admin_actions.py @@ -12,8 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -"""The rescue mode extension.""" - import traceback import webob @@ -40,12 +38,12 @@ class Admin_actions(extensions.ExtensionDescriptor): alias = "os-admin-actions" namespace = "http://docs.openstack.org/ext/admin-actions/api/v1.1" updated = "2011-09-20T00:00:00+00:00" + admin_only = True def __init__(self, ext_mgr): super(Admin_actions, self).__init__(ext_mgr) self.compute_api = compute.API() - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _pause(self, input_dict, req, id): @@ -60,7 +58,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _unpause(self, input_dict, req, id): @@ -75,7 +72,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _suspend(self, input_dict, req, id): @@ -90,7 +86,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _resume(self, input_dict, req, id): @@ -105,7 +100,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _migrate(self, input_dict, req, id): @@ -117,7 +111,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPBadRequest() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _reset_network(self, input_dict, req, id): @@ -132,7 +125,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _inject_network_info(self, input_dict, req, id): @@ -149,7 +141,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _lock(self, input_dict, req, id): @@ -166,7 +157,6 @@ class Admin_actions(extensions.ExtensionDescriptor): raise exc.HTTPUnprocessableEntity() return webob.Response(status_int=202) - @extensions.admin_only @exception.novaclient_converter @scheduler_api.redirect_handler def _unlock(self, input_dict, req, id): @@ -185,6 +175,7 @@ class Admin_actions(extensions.ExtensionDescriptor): def get_actions(self): actions = [ + #TODO(bcwaldon): These actions should be prefixed with 'os-' extensions.ActionExtension("servers", "pause", self._pause), extensions.ActionExtension("servers", "unpause", self._unpause), extensions.ActionExtension("servers", "suspend", self._suspend), diff --git a/nova/api/openstack/v2/contrib/extended_status.py b/nova/api/openstack/v2/contrib/extended_status.py index 5e0a69780..d2e2fe914 100644 --- a/nova/api/openstack/v2/contrib/extended_status.py +++ b/nova/api/openstack/v2/contrib/extended_status.py @@ -14,9 +14,6 @@ """The Extended Status Admin API extension.""" -import traceback - -import webob from webob import exc from nova.api.openstack.v2 import extensions @@ -38,6 +35,7 @@ class Extended_status(extensions.ExtensionDescriptor): alias = "OS-EXT-STS" namespace = "http://docs.openstack.org/ext/extended_status/api/v1.1" updated = "2011-11-03T00:00:00+00:00" + admin_only = True def get_request_extensions(self): request_extensions = [] @@ -69,6 +67,8 @@ class Extended_status(extensions.ExtensionDescriptor): explanation = _("Server not found.") raise exc.HTTPNotFound(explanation=explanation) + #TODO(bcwaldon): these attributes should be prefixed with + # something specific to this extension for state in ['task_state', 'vm_state', 'power_state']: key = "%s:%s" % (Extended_status.alias, state) server[key] = inst_ref[state] @@ -87,11 +87,10 @@ class Extended_status(extensions.ExtensionDescriptor): _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) + req_ext = extensions.RequestExtension('GET', + '/:(project_id)/servers/:(id)', + _extended_status_handler) + request_extensions.append(req_ext) return request_extensions diff --git a/nova/api/openstack/v2/contrib/hosts.py b/nova/api/openstack/v2/contrib/hosts.py index b422a80ab..bf1dd10da 100644 --- a/nova/api/openstack/v2/contrib/hosts.py +++ b/nova/api/openstack/v2/contrib/hosts.py @@ -19,7 +19,6 @@ import webob.exc from xml.dom import minidom from xml.parsers import expat -from nova.api.openstack import common from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova.api.openstack.v2 import extensions @@ -107,15 +106,12 @@ class HostController(object): raise webob.exc.HTTPBadRequest(explanation=e.msg) return {"host": host, "power_action": result} - @extensions.admin_only def startup(self, req, id): return self._host_power_action(req, host=id, action="startup") - @extensions.admin_only def shutdown(self, req, id): return self._host_power_action(req, host=id, action="shutdown") - @extensions.admin_only def reboot(self, req, id): return self._host_power_action(req, host=id, action="reboot") @@ -179,12 +175,13 @@ class HostDeserializer(wsgi.XMLDeserializer): class Hosts(extensions.ExtensionDescriptor): - """Host administration""" + """Admin-only host administration""" name = "Hosts" alias = "os-hosts" namespace = "http://docs.openstack.org/ext/hosts/api/v1.1" updated = "2011-06-29T00:00:00+00:00" + admin_only = True def get_resources(self): body_serializers = { diff --git a/nova/api/openstack/v2/contrib/simple_tenant_usage.py b/nova/api/openstack/v2/contrib/simple_tenant_usage.py index 4204c53bf..1d3053420 100644 --- a/nova/api/openstack/v2/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/v2/contrib/simple_tenant_usage.py @@ -19,14 +19,11 @@ from datetime import datetime import urlparse import webob -from webob import exc from nova.api.openstack.v2 import extensions -from nova.api.openstack.v2 import views from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova.compute import api -from nova.db.sqlalchemy.session import get_session from nova import exception from nova import flags @@ -181,7 +178,7 @@ class SimpleTenantUsageController(object): """Retrive tenant_usage for all tenants""" context = req.environ['nova.context'] - if not context.is_admin and FLAGS.allow_admin_api: + if not context.is_admin: return webob.Response(status_int=403) (period_start, period_stop, detailed) = self._get_datetime_range(req) @@ -196,7 +193,7 @@ class SimpleTenantUsageController(object): tenant_id = id context = req.environ['nova.context'] - if not context.is_admin and FLAGS.allow_admin_api: + if not context.is_admin: if tenant_id != context.project_id: return webob.Response(status_int=403) @@ -261,6 +258,7 @@ class Simple_tenant_usage(extensions.ExtensionDescriptor): alias = "os-simple-tenant-usage" namespace = "http://docs.openstack.org/ext/os-simple-tenant-usage/api/v1.1" updated = "2011-08-19T00:00:00+00:00" + admin_only = True def get_resources(self): resources = [] diff --git a/nova/api/openstack/v2/extensions.py b/nova/api/openstack/v2/extensions.py index b3fbfc923..b6ed897cb 100644 --- a/nova/api/openstack/v2/extensions.py +++ b/nova/api/openstack/v2/extensions.py @@ -16,19 +16,11 @@ # License for the specific language governing permissions and limitations # under the License. -import functools -import imp -import inspect -import os -import sys - -from lxml import etree import routes import webob.dec import webob.exc import nova.api.openstack.v2 -from nova.api.openstack import common from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova import exception @@ -68,6 +60,10 @@ class ExtensionDescriptor(object): # '2011-01-22T13:25:27-06:00' updated = None + # This attribute causes the extension to load only when + # the admin api is enabled + admin_only = False + def __init__(self, ext_mgr): """Register extension with the extension manager.""" @@ -426,9 +422,15 @@ class ExtensionManager(object): ' '.join(extension.__doc__.strip().split())) LOG.debug(_('Ext namespace: %s'), extension.namespace) LOG.debug(_('Ext updated: %s'), extension.updated) + LOG.debug(_('Ext admin_only: %s'), extension.admin_only) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) return False + + # Don't load admin api extensions if the admin api isn't enabled + if not FLAGS.allow_admin_api and extension.admin_only: + return False + return True def load_extension(self, ext_factory): @@ -443,6 +445,7 @@ class ExtensionManager(object): LOG.debug(_("Loading extension %s"), ext_factory) # Load the factory + factory = utils.import_class(ext_factory) # Call it @@ -452,7 +455,9 @@ class ExtensionManager(object): def _load_extensions(self): """Load extensions specified on the command line.""" - for ext_factory in FLAGS.osapi_extension: + extensions = list(FLAGS.osapi_extension) + + for ext_factory in extensions: try: self.load_extension(ext_factory) except Exception as exc: @@ -542,16 +547,6 @@ class ExtensionsXMLSerializer(xmlutil.XMLTemplateSerializer): return ExtensionTemplate() -def admin_only(fnc): - @functools.wraps(fnc) - def _wrapped(self, *args, **kwargs): - if FLAGS.allow_admin_api: - return fnc(self, *args, **kwargs) - raise webob.exc.HTTPNotFound() - _wrapped.func_name = fnc.func_name - return _wrapped - - def wrap_errors(fn): """Ensure errors are not passed along.""" def wrapped(*args): diff --git a/nova/tests/api/openstack/v2/contrib/test_admin_actions.py b/nova/tests/api/openstack/v2/contrib/test_admin_actions.py index 159314ed9..a7237ae58 100644 --- a/nova/tests/api/openstack/v2/contrib/test_admin_actions.py +++ b/nova/tests/api/openstack/v2/contrib/test_admin_actions.py @@ -20,6 +20,7 @@ import webob from nova import compute from nova import flags from nova import test +from nova import utils from nova.tests.api.openstack import fakes @@ -60,28 +61,19 @@ class AdminActionsTest(test.TestCase): def setUp(self): super(AdminActionsTest, self).setUp() - self.flags(allow_admin_api=True) self.stubs.Set(compute.API, 'get', fake_compute_api_get) + self.UUID = utils.gen_uuid() + self.flags(allow_admin_api=True) for _method in self._methods: self.stubs.Set(compute.API, _method, fake_compute_api) - def test_admin_api_enabled(self): + def test_admin_api_actions(self): + self.maxDiff = None app = fakes.wsgi_app() for _action in self._actions: - req = webob.Request.blank('/v2/fake/servers/abcd/action') + req = webob.Request.blank('/v2/fake/servers/%s/action' % self.UUID) req.method = 'POST' req.body = json.dumps({_action: None}) req.content_type = 'application/json' res = req.get_response(app) self.assertEqual(res.status_int, 202) - - def test_admin_api_disabled(self): - FLAGS.allow_admin_api = False - app = fakes.wsgi_app() - for _action in self._actions: - req = webob.Request.blank('/v2/fake/servers/abcd/action') - req.method = 'POST' - req.body = json.dumps({_action: None}) - req.content_type = 'application/json' - res = req.get_response(app) - self.assertEqual(res.status_int, 404) diff --git a/nova/tests/api/openstack/v2/contrib/test_extendedstatus.py b/nova/tests/api/openstack/v2/contrib/test_extendedstatus.py index 59d44191a..66565650e 100644 --- a/nova/tests/api/openstack/v2/contrib/test_extendedstatus.py +++ b/nova/tests/api/openstack/v2/contrib/test_extendedstatus.py @@ -41,6 +41,7 @@ class ExtendedStatusTest(test.TestCase): self.uuid = '70f6db34-de8d-4fbd-aafb-4065bdfa6114' self.url = '/v2/openstack/servers/%s' % self.uuid fakes.stub_out_nw_api(self.stubs) + self.flags(allow_admin_api=True) self.stubs.Set(compute.api.API, 'routing_get', fake_compute_get) def _make_request(self): @@ -54,8 +55,7 @@ class ExtendedStatusTest(test.TestCase): self.assertEqual(server.get('OS-EXT-STS:power_state'), power_state) self.assertEqual(server.get('OS-EXT-STS:task_state'), task_state) - def test_extended_status_with_admin(self): - self.flags(allow_admin_api=True) + def test_extended_status(self): res = self._make_request() body = json.loads(res.body) @@ -65,19 +65,7 @@ class ExtendedStatusTest(test.TestCase): power_state='empowered', task_state='kayaking') - def test_extended_status_no_admin(self): - self.flags(allow_admin_api=False) - res = self._make_request() - body = json.loads(res.body) - - self.assertEqual(res.status_int, 200) - self.assertServerStates(body['server'], - vm_state=None, - power_state=None, - task_state=None) - def test_extended_status_no_instance_fails(self): - self.flags(allow_admin_api=True) def fake_compute_get(*args, **kwargs): raise exception.InstanceNotFound() diff --git a/nova/tests/api/openstack/v2/contrib/test_hosts.py b/nova/tests/api/openstack/v2/contrib/test_hosts.py new file mode 100644 index 000000000..a537ff2f3 --- /dev/null +++ b/nova/tests/api/openstack/v2/contrib/test_hosts.py @@ -0,0 +1,172 @@ +# Copyright (c) 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 lxml import etree +import webob.exc + +from nova import context +from nova import exception +from nova import flags +from nova import log as logging +from nova import test +from nova.api.openstack.v2.contrib import hosts as os_hosts +from nova.scheduler import api as scheduler_api + + +FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.tests.hosts') +# Simulate the hosts returned by the zone manager. +HOST_LIST = [ + {"host_name": "host_c1", "service": "compute"}, + {"host_name": "host_c2", "service": "compute"}, + {"host_name": "host_v1", "service": "volume"}, + {"host_name": "host_v2", "service": "volume"}] + + +def stub_get_host_list(req): + return HOST_LIST + + +def stub_set_host_enabled(context, host, enabled): + # We'll simulate success and failure by assuming + # that 'host_c1' always succeeds, and 'host_c2' + # always fails + fail = (host == "host_c2") + status = "enabled" if (enabled ^ fail) else "disabled" + return status + + +def stub_host_power_action(context, host, action): + return action + + +class FakeRequest(object): + environ = {"nova.context": context.get_admin_context()} + + +class HostTestCase(test.TestCase): + """Test Case for hosts.""" + + def setUp(self): + super(HostTestCase, self).setUp() + self.controller = os_hosts.HostController() + self.req = FakeRequest() + self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) + self.stubs.Set(self.controller.compute_api, 'set_host_enabled', + stub_set_host_enabled) + self.stubs.Set(self.controller.compute_api, 'host_power_action', + stub_host_power_action) + + def test_list_hosts(self): + """Verify that the compute hosts are returned.""" + hosts = os_hosts._list_hosts(self.req) + self.assertEqual(hosts, HOST_LIST) + + compute_hosts = os_hosts._list_hosts(self.req, "compute") + expected = [host for host in HOST_LIST + if host["service"] == "compute"] + self.assertEqual(compute_hosts, expected) + + def test_disable_host(self): + dis_body = {"status": "disable"} + result_c1 = self.controller.update(self.req, "host_c1", body=dis_body) + self.assertEqual(result_c1["status"], "disabled") + result_c2 = self.controller.update(self.req, "host_c2", body=dis_body) + self.assertEqual(result_c2["status"], "enabled") + + def test_enable_host(self): + en_body = {"status": "enable"} + result_c1 = self.controller.update(self.req, "host_c1", body=en_body) + self.assertEqual(result_c1["status"], "enabled") + result_c2 = self.controller.update(self.req, "host_c2", body=en_body) + self.assertEqual(result_c2["status"], "disabled") + + def test_host_startup(self): + self.flags(allow_admin_api=True) + result = self.controller.startup(self.req, "host_c1") + self.assertEqual(result["power_action"], "startup") + + def test_host_shutdown(self): + self.flags(allow_admin_api=True) + result = self.controller.shutdown(self.req, "host_c1") + self.assertEqual(result["power_action"], "shutdown") + + def test_host_reboot(self): + self.flags(allow_admin_api=True) + result = self.controller.reboot(self.req, "host_c1") + self.assertEqual(result["power_action"], "reboot") + + def test_bad_status_value(self): + bad_body = {"status": "bad"} + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) + + def test_bad_update_key(self): + bad_body = {"crazy": "bad"} + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, + self.req, "host_c1", body=bad_body) + + def test_bad_host(self): + self.assertRaises(exception.HostNotFound, self.controller.update, + self.req, "bogus_host_name", body={"status": "disable"}) + + +class HostSerializerTest(test.TestCase): + def setUp(self): + super(HostSerializerTest, self).setUp() + self.serializer = os_hosts.HostSerializer() + self.deserializer = os_hosts.HostDeserializer() + + def test_index_serializer(self): + text = self.serializer.serialize(HOST_LIST, 'index') + + tree = etree.fromstring(text) + + self.assertEqual('hosts', tree.tag) + self.assertEqual(len(HOST_LIST), len(tree)) + for i in range(len(HOST_LIST)): + self.assertEqual('host', tree[i].tag) + self.assertEqual(HOST_LIST[i]['host_name'], + tree[i].get('host_name')) + self.assertEqual(HOST_LIST[i]['service'], + tree[i].get('service')) + + def test_update_serializer(self): + exemplar = dict(host='host_c1', status='enabled') + text = self.serializer.serialize(exemplar, 'update') + + tree = etree.fromstring(text) + + self.assertEqual('host', tree.tag) + for key, value in exemplar.items(): + self.assertEqual(value, tree.get(key)) + + def test_action_serializer(self): + exemplar = dict(host='host_c1', power_action='reboot') + text = self.serializer.serialize(exemplar) + + tree = etree.fromstring(text) + + self.assertEqual('host', tree.tag) + for key, value in exemplar.items(): + self.assertEqual(value, tree.get(key)) + + def test_update_deserializer(self): + exemplar = dict(status='enabled', foo='bar') + intext = ("\n" + 'enabledbar') + result = self.deserializer.deserialize(intext, action='update') + + self.assertEqual(dict(body=exemplar), result) diff --git a/nova/tests/api/openstack/v2/extensions/foxinsocks.py b/nova/tests/api/openstack/v2/extensions/foxinsocks.py index d2995953a..ba1508668 100644 --- a/nova/tests/api/openstack/v2/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/v2/extensions/foxinsocks.py @@ -26,7 +26,7 @@ class FoxInSocksController(object): return "Try to say this Mr. Knox, sir..." -class Foxinsocks(object): +class Foxinsocks(extensions.ExtensionDescriptor): """The Fox In Socks Extension""" name = "Fox In Socks" diff --git a/nova/tests/api/openstack/v2/test_extensions.py b/nova/tests/api/openstack/v2/test_extensions.py index 650770f50..ea230a062 100644 --- a/nova/tests/api/openstack/v2/test_extensions.py +++ b/nova/tests/api/openstack/v2/test_extensions.py @@ -96,6 +96,7 @@ class ExtensionControllerTest(ExtensionTestCase): def setUp(self): super(ExtensionControllerTest, self).setUp() + self.flags(allow_admin_api=True) self.ext_list = [ "AdminActions", "Createserverext", @@ -306,6 +307,19 @@ class InvalidExtension(object): alias = "THIRD" +class AdminExtension(extensions.ExtensionDescriptor): + """Admin-only extension""" + + name = "Admin Ext" + alias = "ADMIN" + namespace = "http://www.example.com/" + updated = "2011-01-22T13:25:27-06:00" + admin_only = True + + def __init__(self, *args, **kwargs): + pass + + class ExtensionManagerTest(ExtensionTestCase): response_body = "Try to say this Mr. Knox, sir..." @@ -329,6 +343,24 @@ class ExtensionManagerTest(ExtensionTestCase): self.assertTrue('FOXNSOX' in ext_mgr.extensions) self.assertTrue('THIRD' not in ext_mgr.extensions) + def test_admin_extensions(self): + self.flags(allow_admin_api=True) + app = v2.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app) + ext_mgr = ext_midware.ext_mgr + ext_mgr.register(AdminExtension()) + self.assertTrue('FOXNSOX' in ext_mgr.extensions) + self.assertTrue('ADMIN' in ext_mgr.extensions) + + def test_admin_extensions_no_admin_api(self): + self.flags(allow_admin_api=False) + app = v2.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app) + ext_mgr = ext_midware.ext_mgr + ext_mgr.register(AdminExtension()) + self.assertTrue('FOXNSOX' in ext_mgr.extensions) + self.assertTrue('ADMIN' not in ext_mgr.extensions) + class ActionExtensionTest(ExtensionTestCase): diff --git a/nova/tests/test_hosts.py b/nova/tests/test_hosts.py deleted file mode 100644 index a537ff2f3..000000000 --- a/nova/tests/test_hosts.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright (c) 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 lxml import etree -import webob.exc - -from nova import context -from nova import exception -from nova import flags -from nova import log as logging -from nova import test -from nova.api.openstack.v2.contrib import hosts as os_hosts -from nova.scheduler import api as scheduler_api - - -FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.tests.hosts') -# Simulate the hosts returned by the zone manager. -HOST_LIST = [ - {"host_name": "host_c1", "service": "compute"}, - {"host_name": "host_c2", "service": "compute"}, - {"host_name": "host_v1", "service": "volume"}, - {"host_name": "host_v2", "service": "volume"}] - - -def stub_get_host_list(req): - return HOST_LIST - - -def stub_set_host_enabled(context, host, enabled): - # We'll simulate success and failure by assuming - # that 'host_c1' always succeeds, and 'host_c2' - # always fails - fail = (host == "host_c2") - status = "enabled" if (enabled ^ fail) else "disabled" - return status - - -def stub_host_power_action(context, host, action): - return action - - -class FakeRequest(object): - environ = {"nova.context": context.get_admin_context()} - - -class HostTestCase(test.TestCase): - """Test Case for hosts.""" - - def setUp(self): - super(HostTestCase, self).setUp() - self.controller = os_hosts.HostController() - self.req = FakeRequest() - self.stubs.Set(scheduler_api, 'get_host_list', stub_get_host_list) - self.stubs.Set(self.controller.compute_api, 'set_host_enabled', - stub_set_host_enabled) - self.stubs.Set(self.controller.compute_api, 'host_power_action', - stub_host_power_action) - - def test_list_hosts(self): - """Verify that the compute hosts are returned.""" - hosts = os_hosts._list_hosts(self.req) - self.assertEqual(hosts, HOST_LIST) - - compute_hosts = os_hosts._list_hosts(self.req, "compute") - expected = [host for host in HOST_LIST - if host["service"] == "compute"] - self.assertEqual(compute_hosts, expected) - - def test_disable_host(self): - dis_body = {"status": "disable"} - result_c1 = self.controller.update(self.req, "host_c1", body=dis_body) - self.assertEqual(result_c1["status"], "disabled") - result_c2 = self.controller.update(self.req, "host_c2", body=dis_body) - self.assertEqual(result_c2["status"], "enabled") - - def test_enable_host(self): - en_body = {"status": "enable"} - result_c1 = self.controller.update(self.req, "host_c1", body=en_body) - self.assertEqual(result_c1["status"], "enabled") - result_c2 = self.controller.update(self.req, "host_c2", body=en_body) - self.assertEqual(result_c2["status"], "disabled") - - def test_host_startup(self): - self.flags(allow_admin_api=True) - result = self.controller.startup(self.req, "host_c1") - self.assertEqual(result["power_action"], "startup") - - def test_host_shutdown(self): - self.flags(allow_admin_api=True) - result = self.controller.shutdown(self.req, "host_c1") - self.assertEqual(result["power_action"], "shutdown") - - def test_host_reboot(self): - self.flags(allow_admin_api=True) - result = self.controller.reboot(self.req, "host_c1") - self.assertEqual(result["power_action"], "reboot") - - def test_bad_status_value(self): - bad_body = {"status": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) - - def test_bad_update_key(self): - bad_body = {"crazy": "bad"} - self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update, - self.req, "host_c1", body=bad_body) - - def test_bad_host(self): - self.assertRaises(exception.HostNotFound, self.controller.update, - self.req, "bogus_host_name", body={"status": "disable"}) - - -class HostSerializerTest(test.TestCase): - def setUp(self): - super(HostSerializerTest, self).setUp() - self.serializer = os_hosts.HostSerializer() - self.deserializer = os_hosts.HostDeserializer() - - def test_index_serializer(self): - text = self.serializer.serialize(HOST_LIST, 'index') - - tree = etree.fromstring(text) - - self.assertEqual('hosts', tree.tag) - self.assertEqual(len(HOST_LIST), len(tree)) - for i in range(len(HOST_LIST)): - self.assertEqual('host', tree[i].tag) - self.assertEqual(HOST_LIST[i]['host_name'], - tree[i].get('host_name')) - self.assertEqual(HOST_LIST[i]['service'], - tree[i].get('service')) - - def test_update_serializer(self): - exemplar = dict(host='host_c1', status='enabled') - text = self.serializer.serialize(exemplar, 'update') - - tree = etree.fromstring(text) - - self.assertEqual('host', tree.tag) - for key, value in exemplar.items(): - self.assertEqual(value, tree.get(key)) - - def test_action_serializer(self): - exemplar = dict(host='host_c1', power_action='reboot') - text = self.serializer.serialize(exemplar) - - tree = etree.fromstring(text) - - self.assertEqual('host', tree.tag) - for key, value in exemplar.items(): - self.assertEqual(value, tree.get(key)) - - def test_update_deserializer(self): - exemplar = dict(status='enabled', foo='bar') - intext = ("\n" - 'enabledbar') - result = self.deserializer.deserialize(intext, action='update') - - self.assertEqual(dict(body=exemplar), result) -- cgit