diff options
| author | Andrea Rosa <andrea.rosa@hp.com> | 2013-02-20 10:10:04 +0000 |
|---|---|---|
| committer | Michael Still <mikal@stillhq.com> | 2013-06-12 11:27:25 +1000 |
| commit | c741e862fd35d28338d7966e03bf662c8fb65dac (patch) | |
| tree | dbb6cd67e829fc5836d4ec1fc27b101e07c67fbe /nova/tests | |
| parent | d7f898eab900de8f804285333e6fb921294520fc (diff) | |
Give a way to save why a service has been disabled.
Implements blueprint record-reason-for-disabling-service
We added a field to the service table to log a reason when a service has
been disabled.
We added a new API extension called os-extended-services. The new extension
will extend the os-services extension adding:
- A method for disabling a service and specify a reason for that.
PUT /v2/{tenant_id}/os-services/disable-log-reason
When the os-extended-extension is loaded the call:
GET /V2/{tenant_id}/os-services
will return the list of services with reason information it that exists.
DocImpact
Change-Id: I87a4affc45160796ff11c7b03e591e6aba73d62a
Diffstat (limited to 'nova/tests')
12 files changed, 333 insertions, 35 deletions
diff --git a/nova/tests/api/openstack/compute/contrib/test_services.py b/nova/tests/api/openstack/compute/contrib/test_services.py index 387a5eb21..c9d0c76e5 100644 --- a/nova/tests/api/openstack/compute/contrib/test_services.py +++ b/nova/tests/api/openstack/compute/contrib/test_services.py @@ -14,8 +14,10 @@ import datetime +import webob.exc from nova.api.openstack.compute.contrib import services +from nova.api.openstack import extensions from nova import availability_zones from nova import context from nova import db @@ -33,28 +35,32 @@ fake_services_list = [ 'disabled': True, 'topic': 'scheduler', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2), - 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 27)}, + 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 27), + 'disabled_reason': 'test1'}, {'binary': 'nova-compute', 'host': 'host1', 'id': 2, 'disabled': True, 'topic': 'compute', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5), - 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 27)}, + 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 27), + 'disabled_reason': 'test2'}, {'binary': 'nova-scheduler', 'host': 'host2', 'id': 3, 'disabled': False, 'topic': 'scheduler', 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34), - 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 28)}, + 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 28), + 'disabled_reason': ''}, {'binary': 'nova-compute', 'host': 'host2', 'id': 4, 'disabled': True, 'topic': 'compute', 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38), - 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 28)}, + 'created_at': datetime.datetime(2012, 9, 18, 2, 46, 28), + 'disabled_reason': 'test4'}, ] @@ -106,9 +112,6 @@ def fake_service_update(context, service_id, values): service = fake_service_get_by_id(service_id) if service is None: raise exception.ServiceNotFound(service_id=service_id) - else: - {'host': 'host1', 'service': 'nova-compute', - 'disabled': values['disabled']} def fake_utcnow(): @@ -121,8 +124,9 @@ class ServicesTest(test.TestCase): super(ServicesTest, self).setUp() self.context = context.get_admin_context() - self.controller = services.ServiceController() - + self.ext_mgr = extensions.ExtensionManager() + self.ext_mgr.extensions = {} + self.controller = services.ServiceController(self.ext_mgr) self.stubs.Set(self.controller.host_api, "service_get_all", fake_host_api_service_get_all) self.stubs.Set(timeutils, "utcnow", fake_utcnow) @@ -134,21 +138,30 @@ class ServicesTest(test.TestCase): req = FakeRequest() res_dict = self.controller.index(req) - response = {'services': [{'binary': 'nova-scheduler', - 'host': 'host1', 'zone': 'internal', - 'status': 'disabled', 'state': 'up', + response = {'services': [ + {'binary': 'nova-scheduler', + 'host': 'host1', + 'zone': 'internal', + 'status': 'disabled', + 'state': 'up', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)}, {'binary': 'nova-compute', - 'host': 'host1', 'zone': 'nova', - 'status': 'disabled', 'state': 'up', + 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'up', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}, - {'binary': 'nova-scheduler', 'host': 'host2', + {'binary': 'nova-scheduler', + 'host': 'host2', 'zone': 'internal', - 'status': 'enabled', 'state': 'down', + 'status': 'enabled', + 'state': 'down', 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34)}, - {'binary': 'nova-compute', 'host': 'host2', + {'binary': 'nova-compute', + 'host': 'host2', 'zone': 'nova', - 'status': 'disabled', 'state': 'down', + 'status': 'disabled', + 'state': 'down', 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]} self.assertEqual(res_dict, response) @@ -156,13 +169,18 @@ class ServicesTest(test.TestCase): req = FakeRequestWithHost() res_dict = self.controller.index(req) - response = {'services': [{'binary': 'nova-scheduler', 'host': 'host1', + response = {'services': [ + {'binary': 'nova-scheduler', + 'host': 'host1', 'zone': 'internal', - 'status': 'disabled', 'state': 'up', + 'status': 'disabled', + 'state': 'up', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)}, - {'binary': 'nova-compute', 'host': 'host1', + {'binary': 'nova-compute', + 'host': 'host1', 'zone': 'nova', - 'status': 'disabled', 'state': 'up', + 'status': 'disabled', + 'state': 'up', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]} self.assertEqual(res_dict, response) @@ -170,13 +188,18 @@ class ServicesTest(test.TestCase): req = FakeRequestWithService() res_dict = self.controller.index(req) - response = {'services': [{'binary': 'nova-compute', 'host': 'host1', + response = {'services': [ + {'binary': 'nova-compute', + 'host': 'host1', 'zone': 'nova', - 'status': 'disabled', 'state': 'up', + 'status': 'disabled', + 'state': 'up', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}, - {'binary': 'nova-compute', 'host': 'host2', + {'binary': 'nova-compute', + 'host': 'host2', 'zone': 'nova', - 'status': 'disabled', 'state': 'down', + 'status': 'disabled', + 'state': 'down', 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]} self.assertEqual(res_dict, response) @@ -184,25 +207,125 @@ class ServicesTest(test.TestCase): req = FakeRequestWithHostService() res_dict = self.controller.index(req) - response = {'services': [{'binary': 'nova-compute', 'host': 'host1', + response = {'services': [ + {'binary': 'nova-compute', + 'host': 'host1', 'zone': 'nova', - 'status': 'disabled', 'state': 'up', + 'status': 'disabled', + 'state': 'up', 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]} self.assertEqual(res_dict, response) + def test_services_detail(self): + self.ext_mgr.extensions['os-extended-services'] = True + self.controller = services.ServiceController(self.ext_mgr) + self.stubs.Set(self.controller.host_api, "service_get_all", + fake_host_api_service_get_all) + req = FakeRequest() + res_dict = self.controller.index(req) + response = {'services': [ + {'binary': 'nova-scheduler', + 'host': 'host1', + 'zone': 'internal', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2), + 'disabled_reason': 'test1'}, + {'binary': 'nova-compute', + 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5), + 'disabled_reason': 'test2'}, + {'binary': 'nova-scheduler', + 'host': 'host2', + 'zone': 'internal', + 'status': 'enabled', + 'state': 'down', + 'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34), + 'disabled_reason': ''}, + {'binary': 'nova-compute', + 'host': 'host2', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38), + 'disabled_reason': 'test4'}]} + self.assertEqual(res_dict, response) + + def test_service_detail_with_host(self): + self.ext_mgr.extensions['os-extended-services'] = True + self.controller = services.ServiceController(self.ext_mgr) + self.stubs.Set(self.controller.host_api, "service_get_all", + fake_host_api_service_get_all) + req = FakeRequestWithHost() + res_dict = self.controller.index(req) + response = {'services': [ + {'binary': 'nova-scheduler', + 'host': 'host1', + 'zone': 'internal', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2), + 'disabled_reason': 'test1'}, + {'binary': 'nova-compute', + 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5), + 'disabled_reason': 'test2'}]} + self.assertEqual(res_dict, response) + + def test_service_detail_with_service(self): + self.ext_mgr.extensions['os-extended-services'] = True + self.controller = services.ServiceController(self.ext_mgr) + self.stubs.Set(self.controller.host_api, "service_get_all", + fake_host_api_service_get_all) + req = FakeRequestWithService() + res_dict = self.controller.index(req) + response = {'services': [ + {'binary': 'nova-compute', + 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5), + 'disabled_reason': 'test2'}, + {'binary': 'nova-compute', + 'host': 'host2', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'down', + 'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38), + 'disabled_reason': 'test4'}]} + self.assertEqual(res_dict, response) + + def test_service_detail_with_host_service(self): + self.ext_mgr.extensions['os-extended-services'] = True + self.controller = services.ServiceController(self.ext_mgr) + self.stubs.Set(self.controller.host_api, "service_get_all", + fake_host_api_service_get_all) + req = FakeRequestWithHostService() + res_dict = self.controller.index(req) + response = {'services': [ + {'binary': 'nova-compute', + 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5), + 'disabled_reason': 'test2'}]} + self.assertEqual(res_dict, response) + def test_services_enable(self): body = {'host': 'host1', 'binary': 'nova-compute'} req = fakes.HTTPRequest.blank('/v2/fake/os-services/enable') res_dict = self.controller.update(req, "enable", body) self.assertEqual(res_dict['service']['status'], 'enabled') - - def test_services_disable(self): - req = fakes.HTTPRequest.blank('/v2/fake/os-services/disable') - body = {'host': 'host1', 'binary': 'nova-compute'} - res_dict = self.controller.update(req, "disable", body) - - self.assertEqual(res_dict['service']['status'], 'disabled') + self.assertFalse('disabled_reason' in res_dict['service']) # This test is just to verify that the servicegroup API gets used when # calling this API. @@ -213,3 +336,44 @@ class ServicesTest(test.TestCase): self.stubs.Set(db_driver.DbDriver, 'is_up', dummy_is_up) req = FakeRequestWithHostService() self.assertRaises(KeyError, self.controller.index, req) + + def test_services_disable(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-services/disable') + body = {'host': 'host1', 'binary': 'nova-compute'} + res_dict = self.controller.update(req, "disable", body) + + self.assertEqual(res_dict['service']['status'], 'disabled') + self.assertFalse('disabled_reason' in res_dict['service']) + + def test_services_disable_log_reason(self): + self.ext_mgr.extensions['os-extended-services'] = True + self.controller = services.ServiceController(self.ext_mgr) + req = \ + fakes.HTTPRequest.blank('v2/fakes/os-services/disable-log-reason') + body = {'host': 'host1', + 'binary': 'nova-compute', + 'disabled_reason': 'test-reason', + } + res_dict = self.controller.update(req, "disable-log-reason", body) + + self.assertEqual(res_dict['service']['status'], 'disabled') + self.assertEqual(res_dict['service']['disabled_reason'], 'test-reason') + + def test_mandatory_reason_field(self): + self.ext_mgr.extensions['os-extended-services'] = True + self.controller = services.ServiceController(self.ext_mgr) + req = \ + fakes.HTTPRequest.blank('v2/fakes/os-services/disable-log-reason') + body = {'host': 'host1', + 'binary': 'nova-compute', + } + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.update, req, "disable-log-reason", body) + + def test_invalid_reason_field(self): + reason = ' ' + self.assertFalse(self.controller._is_valid_as_reason(reason)) + reason = 'a' * 256 + self.assertFalse(self.controller._is_valid_as_reason(reason)) + reason = 'it\'s a valid reason.' + self.assertTrue(self.controller._is_valid_as_reason(reason)) diff --git a/nova/tests/cells/test_cells_messaging.py b/nova/tests/cells/test_cells_messaging.py index 9aae11201..d4d9b052e 100644 --- a/nova/tests/cells/test_cells_messaging.py +++ b/nova/tests/cells/test_cells_messaging.py @@ -784,6 +784,7 @@ class CellsTargetedMethodsTestCase(test.TestCase): result = response.value_or_raise() result.pop('created_at', None) result.pop('updated_at', None) + result.pop('disabled_reason', None) expected_result = dict( deleted=0, deleted_at=None, binary=fake_service['binary'], diff --git a/nova/tests/db/test_migrations.py b/nova/tests/db/test_migrations.py index c73093d7d..3efa8c6bc 100644 --- a/nova/tests/db/test_migrations.py +++ b/nova/tests/db/test_migrations.py @@ -1626,6 +1626,16 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn): # check that groups does not exist self._check_no_group_instance_tables(engine) + def _check_188(self, engine, data): + services = db_utils.get_table(engine, 'services') + rows = services.select().execute().fetchall() + self.assertEqual(rows[0]['disabled_reason'], None) + + def _post_downgrade_188(self, engine): + services = db_utils.get_table(engine, 'services') + rows = services.select().execute().fetchall() + self.assertFalse('disabled_reason' in rows[0]) + class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn): """Test sqlalchemy-migrate migrations.""" diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index 1fbe71a90..33ddb3fab 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -361,6 +361,14 @@ "updated": "%(timestamp)s" }, { + "alias": "os-extended-services", + "description": "%(text)s", + "links": [], + "name": "ExtendedServices", + "namespace": "http://docs.openstack.org/compute/ext/extended_services/api/v2", + "updated": "%(timestamp)s" + }, + { "alias": "os-fping", "description": "%(text)s", "links": [], diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index f0a802f30..bbbef2c2c 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -135,6 +135,9 @@ <extension alias="os-services" name="Services" namespace="http://docs.openstack.org/compute/ext/services/api/v2" updated="%(timestamp)s"> <description>%(text)s</description> </extension> + <extension alias="os-extended-services" name="ExtendedServices" namespace="http://docs.openstack.org/compute/ext/extended_services/api/v2" updated="%(timestamp)s"> + <description>%(text)s</description> + </extension> <extension alias="os-fping" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/fping/api/v1.1" name="Fping"> <description>%(text)s</description> </extension> diff --git a/nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.json.tpl b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.json.tpl new file mode 100644 index 000000000..13ba2f11c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.json.tpl @@ -0,0 +1,5 @@ +{ + "host": "%(host)s", + "binary": "%(binary)s", + "disabled_reason": "%(disabled_reason)s" +} diff --git a/nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.xml.tpl b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.xml.tpl new file mode 100644 index 000000000..a1ffd7e20 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.xml.tpl @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<service host="%(host)s" binary="%(binary)s" disabled_reason="%(disabled_reason)s"/> diff --git a/nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.json.tpl b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.json.tpl new file mode 100644 index 000000000..5266b0b62 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.json.tpl @@ -0,0 +1,8 @@ +{ + "service": { + "binary": "%(binary)s", + "host": "%(host)s", + "disabled_reason": "%(disabled_reason)s", + "status": "disabled" + } +} diff --git a/nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.xml.tpl b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.xml.tpl new file mode 100644 index 000000000..f7255d385 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.xml.tpl @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<service status="disabled" binary="%(binary)s" host="%(host)s" disabled_reason="%(disabled_reason)s"/> diff --git a/nova/tests/integrated/api_samples/os-services/services-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-services/services-get-resp.json.tpl new file mode 100644 index 000000000..39eda4e72 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-services/services-get-resp.json.tpl @@ -0,0 +1,40 @@ +{ + "services": [ + { + "binary": "nova-scheduler", + "host": "host1", + "disabled_reason": "test1", + "state": "up", + "status": "disabled", + "updated_at": "%(timestamp)s", + "zone": "internal" + }, + { + "binary": "nova-compute", + "host": "host1", + "disabled_reason": "test2", + "state": "up", + "status": "disabled", + "updated_at": "%(timestamp)s", + "zone": "nova" + }, + { + "binary": "nova-scheduler", + "host": "host2", + "disabled_reason": "", + "state": "down", + "status": "enabled", + "updated_at": "%(timestamp)s", + "zone": "internal" + }, + { + "binary": "nova-compute", + "host": "host2", + "disabled_reason": "test4", + "state": "down", + "status": "disabled", + "updated_at": "%(timestamp)s", + "zone": "nova" + } + ] +} diff --git a/nova/tests/integrated/api_samples/os-services/services-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-services/services-get-resp.xml.tpl new file mode 100644 index 000000000..53f8541ad --- /dev/null +++ b/nova/tests/integrated/api_samples/os-services/services-get-resp.xml.tpl @@ -0,0 +1,7 @@ +<?xml version='1.0' encoding='UTF-8'?> +<services> + <service status="disabled" binary="nova-scheduler" zone="internal" state="up" updated_at="%(timestamp)s" host="host1" disabled_reason="test1"/> + <service status="disabled" binary="nova-compute" zone="nova" state="up" updated_at="%(timestamp)s" host="host1" disabled_reason="test2"/> + <service status="enabled" binary="nova-scheduler" zone="internal" state="down" updated_at="%(timestamp)s" host="host2" disabled_reason=""/> + <service status="disabled" binary="nova-compute" zone="nova" state="down" updated_at="%(timestamp)s" host="host2" disabled_reason="test4"/> +</services> diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 0d8c869f2..b929949d3 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -30,6 +30,7 @@ from oslo.config import cfg from nova.api.metadata import password from nova.api.openstack.compute.contrib import coverage_ext from nova.api.openstack.compute.contrib import fping +from nova.api.openstack.compute.extensions import ExtensionManager as ext_mgr # Import extensions to pull in osapi_compute_extension CONF option used below. from nova.cells import state from nova.cloudpipe import pipelib @@ -1965,6 +1966,9 @@ class ServicesJsonTest(ApiSampleTestBase): super(ServicesJsonTest, self).tearDown() timeutils.clear_time_override() + def fake_load(self, *args): + return True + def test_services_list(self): """Return a list of all agent builds.""" response = self._do_get('os-services') @@ -1996,11 +2000,55 @@ class ServicesJsonTest(ApiSampleTestBase): "binary": "nova-compute"} self._verify_response('service-disable-put-resp', subs, response, 200) + def test_service_detail(self): + """ + Return a list of all running services with the disable reason + information if that exists. + """ + self.stubs.Set(ext_mgr, "is_loaded", self.fake_load) + response = self._do_get('os-services') + self.assertEqual(response.status, 200) + subs = {'binary': 'nova-compute', + 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', + 'state': 'up'} + subs.update(self._get_regexes()) + return self._verify_response('services-get-resp', + subs, response, 200) + + def test_service_disable_log_reason(self): + """Disable an existing service and log the reason.""" + self.stubs.Set(ext_mgr, "is_loaded", self.fake_load) + subs = {"host": "host1", + 'binary': 'nova-compute', + 'disabled_reason': 'test2'} + response = self._do_put('os-services/disable-log-reason', + 'service-disable-log-put-req', subs) + return self._verify_response('service-disable-log-put-resp', + subs, response, 200) + class ServicesXmlTest(ServicesJsonTest): ctype = 'xml' +class ExtendedServicesJsonTest(ApiSampleTestBase): + """ + This extension is extending the functionalities of the + Services extension so the funcionalities introduced by this extension + are tested in the ServicesJsonTest and ServicesXmlTest classes. + """ + + extension_name = ("nova.api.openstack.compute.contrib." + "extended_services.Extended_services") + + +class ExtendedServicesXmlTest(ExtendedServicesJsonTest): + """This extension is tested in the ServicesXmlTest class.""" + ctype = 'xml' + + class SimpleTenantUsageSampleJsonTest(ServersSampleBase): extension_name = ("nova.api.openstack.compute.contrib.simple_tenant_usage." "Simple_tenant_usage") |
