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
			
			
This commit is contained in:
		 Andrea Rosa
					Andrea Rosa
				
			
				
					committed by
					
						 Michael Still
						Michael Still
					
				
			
			
				
	
			
			
			 Michael Still
						Michael Still
					
				
			
						parent
						
							d7f898eab9
						
					
				
				
					commit
					c741e862fd
				
			| @@ -496,6 +496,14 @@ | |||||||
|             "namespace": "http://docs.openstack.org/compute/ext/services/api/v2", |             "namespace": "http://docs.openstack.org/compute/ext/services/api/v2", | ||||||
|             "updated": "2012-10-28T00:00:00-00:00" |             "updated": "2012-10-28T00:00:00-00:00" | ||||||
|         }, |         }, | ||||||
|  |         { | ||||||
|  |             "alias": "os-extended-services", | ||||||
|  |             "description": "Extended services support.", | ||||||
|  |             "links": [], | ||||||
|  |             "name": "ExtendedServices", | ||||||
|  |             "namespace": "http://docs.openstack.org/compute/ext/extended_services/api/v2", | ||||||
|  |             "updated": "2013-05-17T00:00:00-00:00" | ||||||
|  |         }, | ||||||
|         { |         { | ||||||
|             "alias": "os-simple-tenant-usage", |             "alias": "os-simple-tenant-usage", | ||||||
|             "description": "Simple tenant usage extension.", |             "description": "Simple tenant usage extension.", | ||||||
|   | |||||||
| @@ -204,6 +204,9 @@ | |||||||
|   <extension alias="os-services" updated="2012-10-28T00:00:00-00:00" namespace="http://docs.openstack.org/compute/ext/services/api/v2" name="Services"> |   <extension alias="os-services" updated="2012-10-28T00:00:00-00:00" namespace="http://docs.openstack.org/compute/ext/services/api/v2" name="Services"> | ||||||
|     <description>Services support.</description> |     <description>Services support.</description> | ||||||
|   </extension> |   </extension> | ||||||
|  |   <extension alias="os-extended-services" updated="2013-05-17T00:00:00-00:00" namespace="http://docs.openstack.org/compute/ext/extended_services/api/v2" name="ExtendedServices"> | ||||||
|  |     <description>Extended services support.</description> | ||||||
|  |   </extension> | ||||||
|   <extension alias="os-simple-tenant-usage" updated="2011-08-19T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1" name="SimpleTenantUsage"> |   <extension alias="os-simple-tenant-usage" updated="2011-08-19T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/os-simple-tenant-usage/api/v1.1" name="SimpleTenantUsage"> | ||||||
|     <description>Simple tenant usage extension.</description> |     <description>Simple tenant usage extension.</description> | ||||||
|   </extension> |   </extension> | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | { | ||||||
|  |     "host": "host1", | ||||||
|  |     "binary": "nova-compute", | ||||||
|  |     "disabled_reason": "test2" | ||||||
|  | } | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | <?xml version='1.0' encoding='UTF-8'?> | ||||||
|  | <service host="host1" binary="nova-compute" disabled_reason="test2"/> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |     "service": { | ||||||
|  |         "binary": "nova-compute", | ||||||
|  |         "host": "host1", | ||||||
|  |         "disabled_reason": "test2", | ||||||
|  |         "status": "disabled" | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | <?xml version='1.0' encoding='UTF-8'?> | ||||||
|  | <service host="host1" binary="nova-compute" status="disabled" disabled_reason="test2" /> | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| { | { | ||||||
|     "host": "host1", |     "host": "host1", | ||||||
|     "service": "nova-compute" |     "binary": "nova-compute" | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								doc/api_samples/os-services/services-get-resp.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								doc/api_samples/os-services/services-get-resp.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | { | ||||||
|  |     "services": [ | ||||||
|  |         { | ||||||
|  |             "binary": "nova-scheduler", | ||||||
|  |             "host": "host1", | ||||||
|  |             "state": "up", | ||||||
|  |             "status": "disabled", | ||||||
|  |             "updated_at": "2012-10-29T13:42:02.000000", | ||||||
|  |             "zone": "internal", | ||||||
|  |             "disabled_reason": "test1" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "binary": "nova-compute", | ||||||
|  |             "host": "host1", | ||||||
|  |             "state": "up", | ||||||
|  |             "status": "disabled", | ||||||
|  |             "updated_at": "2012-10-29T13:42:05.000000", | ||||||
|  |             "zone": "nova", | ||||||
|  |             "disabled_reason": "test2" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "binary": "nova-scheduler", | ||||||
|  |             "host": "host2", | ||||||
|  |             "state": "down", | ||||||
|  |             "status": "enabled", | ||||||
|  |             "updated_at": "2012-09-19T06:55:34.000000", | ||||||
|  |             "zone": "internal", | ||||||
|  |             "disabled_reason": "" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "binary": "nova-compute", | ||||||
|  |             "host": "host2", | ||||||
|  |             "state": "down", | ||||||
|  |             "status": "disabled", | ||||||
|  |             "updated_at": "2012-09-18T08:03:38.000000", | ||||||
|  |             "zone": "nova", | ||||||
|  |             "disabled_reason": "test4" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								doc/api_samples/os-services/services-get-resp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								doc/api_samples/os-services/services-get-resp.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | <services> | ||||||
|  |     <service status="disabled" binary="nova-scheduler" zone="internal" state="up" host="host1" updated_at="2012-10-29T13:42:02.000000" disabled_reason="test1"/> | ||||||
|  |     <service status="disabled" binary="nova-compute" zone="nova" state="up" host="host1" updated_at="2012-10-29T13:42:05.000000" disabled_reason="test2"/> | ||||||
|  |     <service status="enabled" binary="nova-scheduler" zone="internal" state="down" host="host2" updated_at="2012-09-19T06:55:34.000000" disabled_reason=""/> | ||||||
|  |     <service status="disabled" binary="nova-compute" zone="nova" state="down" host="host2" updated_at="2012-09-18T08:03:38.000000" disabled_reason="test4"/> | ||||||
|  | </services> | ||||||
							
								
								
									
										11
									
								
								nova/api/openstack/compute/contrib/extended_services.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								nova/api/openstack/compute/contrib/extended_services.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | from nova.api.openstack import extensions | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Extended_services(extensions.ExtensionDescriptor): | ||||||
|  |     """Extended services support.""" | ||||||
|  |  | ||||||
|  |     name = "ExtendedServices" | ||||||
|  |     alias = "os-extended-services" | ||||||
|  |     namespace = ("http://docs.openstack.org/compute/ext/" | ||||||
|  |                 "extended_services/api/v2") | ||||||
|  |     updated = "2013-05-17T00:00:00-00:00" | ||||||
| @@ -23,6 +23,7 @@ from nova.api.openstack import xmlutil | |||||||
| from nova import compute | from nova import compute | ||||||
| from nova import exception | from nova import exception | ||||||
| from nova import servicegroup | from nova import servicegroup | ||||||
|  | from nova import utils | ||||||
|  |  | ||||||
| authorize = extensions.extension_authorizer('compute', 'services') | authorize = extensions.extension_authorizer('compute', 'services') | ||||||
| CONF = cfg.CONF | CONF = cfg.CONF | ||||||
| @@ -39,6 +40,7 @@ class ServicesIndexTemplate(xmlutil.TemplateBuilder): | |||||||
|         elem.set('status') |         elem.set('status') | ||||||
|         elem.set('state') |         elem.set('state') | ||||||
|         elem.set('updated_at') |         elem.set('updated_at') | ||||||
|  |         elem.set('disabled_reason') | ||||||
|  |  | ||||||
|         return xmlutil.MasterTemplate(root, 1) |         return xmlutil.MasterTemplate(root, 1) | ||||||
|  |  | ||||||
| @@ -49,6 +51,7 @@ class ServiceUpdateTemplate(xmlutil.TemplateBuilder): | |||||||
|         root.set('host') |         root.set('host') | ||||||
|         root.set('binary') |         root.set('binary') | ||||||
|         root.set('status') |         root.set('status') | ||||||
|  |         root.set('disabled_reason') | ||||||
|  |  | ||||||
|         return xmlutil.MasterTemplate(root, 1) |         return xmlutil.MasterTemplate(root, 1) | ||||||
|  |  | ||||||
| @@ -62,21 +65,20 @@ class ServiceUpdateDeserializer(wsgi.XMLDeserializer): | |||||||
|             return service |             return service | ||||||
|         service['host'] = service_node.getAttribute('host') |         service['host'] = service_node.getAttribute('host') | ||||||
|         service['binary'] = service_node.getAttribute('binary') |         service['binary'] = service_node.getAttribute('binary') | ||||||
|  |         service['disabled_reason'] = service_node.getAttribute( | ||||||
|  |                                                     'disabled_reason') | ||||||
|  |  | ||||||
|         return dict(body=service) |         return dict(body=service) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ServiceController(object): | class ServiceController(object): | ||||||
|  |  | ||||||
|     def __init__(self): |     def __init__(self, ext_mgr=None, *args, **kwargs): | ||||||
|         self.host_api = compute.HostAPI() |         self.host_api = compute.HostAPI() | ||||||
|         self.servicegroup_api = servicegroup.API() |         self.servicegroup_api = servicegroup.API() | ||||||
|  |         self.ext_mgr = ext_mgr | ||||||
|  |  | ||||||
|     @wsgi.serializers(xml=ServicesIndexTemplate) |     def _get_services(self, req): | ||||||
|     def index(self, req): |  | ||||||
|         """ |  | ||||||
|         Return a list of all running services. Filter by host & service name. |  | ||||||
|         """ |  | ||||||
|         context = req.environ['nova.context'] |         context = req.environ['nova.context'] | ||||||
|         authorize(context) |         authorize(context) | ||||||
|         services = self.host_api.service_get_all( |         services = self.host_api.service_get_all( | ||||||
| @@ -93,18 +95,49 @@ class ServiceController(object): | |||||||
|         if binary: |         if binary: | ||||||
|             services = [s for s in services if s['binary'] == binary] |             services = [s for s in services if s['binary'] == binary] | ||||||
|  |  | ||||||
|         svcs = [] |         return services | ||||||
|         for svc in services: |  | ||||||
|  |     def _get_service_detail(self, svc, detailed): | ||||||
|         alive = self.servicegroup_api.service_is_up(svc) |         alive = self.servicegroup_api.service_is_up(svc) | ||||||
|             art = (alive and "up") or "down" |         state = (alive and "up") or "down" | ||||||
|         active = 'enabled' |         active = 'enabled' | ||||||
|         if svc['disabled']: |         if svc['disabled']: | ||||||
|             active = 'disabled' |             active = 'disabled' | ||||||
|             svcs.append({"binary": svc['binary'], 'host': svc['host'], |         service_detail = {'binary': svc['binary'], 'host': svc['host'], | ||||||
|                      'zone': svc['availability_zone'], |                      'zone': svc['availability_zone'], | ||||||
|                          'status': active, 'state': art, |                      'status': active, 'state': state, | ||||||
|                          'updated_at': svc['updated_at']}) |                      'updated_at': svc['updated_at']} | ||||||
|         return {'services': svcs} |         if detailed: | ||||||
|  |             service_detail['disabled_reason'] = svc['disabled_reason'] | ||||||
|  |  | ||||||
|  |         return service_detail | ||||||
|  |  | ||||||
|  |     def _get_services_list(self, req, detailed): | ||||||
|  |         services = self._get_services(req) | ||||||
|  |         svcs = [] | ||||||
|  |         for svc in services: | ||||||
|  |             svcs.append(self._get_service_detail(svc, detailed)) | ||||||
|  |  | ||||||
|  |         return svcs | ||||||
|  |  | ||||||
|  |     def _is_valid_as_reason(self, reason): | ||||||
|  |         try: | ||||||
|  |             utils.check_string_length(reason.strip(), 'Disabled reason', | ||||||
|  |                                       min_length=1, max_length=255) | ||||||
|  |         except exception.InvalidInput: | ||||||
|  |             return False | ||||||
|  |  | ||||||
|  |         return True | ||||||
|  |  | ||||||
|  |     @wsgi.serializers(xml=ServicesIndexTemplate) | ||||||
|  |     def index(self, req): | ||||||
|  |         """ | ||||||
|  |         Return a list of all running services. Filter by host & service name. | ||||||
|  |         """ | ||||||
|  |         detailed = self.ext_mgr.is_loaded('os-extended-services') | ||||||
|  |         services = self._get_services_list(req, detailed) | ||||||
|  |  | ||||||
|  |         return {'services': services} | ||||||
|  |  | ||||||
|     @wsgi.deserializers(xml=ServiceUpdateDeserializer) |     @wsgi.deserializers(xml=ServiceUpdateDeserializer) | ||||||
|     @wsgi.serializers(xml=ServiceUpdateTemplate) |     @wsgi.serializers(xml=ServiceUpdateTemplate) | ||||||
| @@ -113,28 +146,49 @@ class ServiceController(object): | |||||||
|         context = req.environ['nova.context'] |         context = req.environ['nova.context'] | ||||||
|         authorize(context) |         authorize(context) | ||||||
|  |  | ||||||
|  |         ext_loaded = self.ext_mgr.is_loaded('os-extended-services') | ||||||
|         if id == "enable": |         if id == "enable": | ||||||
|             disabled = False |             disabled = False | ||||||
|         elif id == "disable": |             status = "enabled" | ||||||
|  |         elif (id == "disable" or | ||||||
|  |                 (id == "disable-log-reason" and ext_loaded)): | ||||||
|             disabled = True |             disabled = True | ||||||
|  |             status = "disabled" | ||||||
|         else: |         else: | ||||||
|             raise webob.exc.HTTPNotFound(_("Unknown action")) |             raise webob.exc.HTTPNotFound("Unknown action") | ||||||
|  |  | ||||||
|         status = id + 'd' |  | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             host = body['host'] |             host = body['host'] | ||||||
|             binary = body['binary'] |             binary = body['binary'] | ||||||
|  |             ret_value = { | ||||||
|  |                 'service': { | ||||||
|  |                     'host': host, | ||||||
|  |                     'binary': binary, | ||||||
|  |                     'status': status, | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |             status_detail = {'disabled': disabled} | ||||||
|  |             if id == "disable-log-reason": | ||||||
|  |                 reason = body['disabled_reason'] | ||||||
|  |                 if not self._is_valid_as_reason(reason): | ||||||
|  |                     msg = _('Disabled reason contains invalid characters ' | ||||||
|  |                             'or is too long') | ||||||
|  |                     raise webob.exc.HTTPUnprocessableEntity(detail=msg) | ||||||
|  |  | ||||||
|  |                 status_detail['disabled_reason'] = reason | ||||||
|  |                 ret_value['service']['disabled_reason'] = reason | ||||||
|         except (TypeError, KeyError): |         except (TypeError, KeyError): | ||||||
|             raise webob.exc.HTTPUnprocessableEntity() |             msg = _('Invalid attribute in the request') | ||||||
|  |             if 'host' in body and 'binary' in body: | ||||||
|  |                 msg = _('Missing disabled reason field') | ||||||
|  |             raise webob.exc.HTTPUnprocessableEntity(detail=msg) | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             svc = self.host_api.service_update(context, host, binary, |             svc = self.host_api.service_update(context, host, binary, | ||||||
|                                                {'disabled': disabled}) |                                                status_detail) | ||||||
|         except exception.ServiceNotFound as exc: |         except exception.ServiceNotFound: | ||||||
|             raise webob.exc.HTTPNotFound(_("Unknown service")) |             raise webob.exc.HTTPNotFound(_("Unknown service")) | ||||||
|  |  | ||||||
|         return {'service': {'host': host, 'binary': binary, 'status': status}} |         return ret_value | ||||||
|  |  | ||||||
|  |  | ||||||
| class Services(extensions.ExtensionDescriptor): | class Services(extensions.ExtensionDescriptor): | ||||||
| @@ -148,6 +202,7 @@ class Services(extensions.ExtensionDescriptor): | |||||||
|     def get_resources(self): |     def get_resources(self): | ||||||
|         resources = [] |         resources = [] | ||||||
|         resource = extensions.ResourceExtension('os-services', |         resource = extensions.ResourceExtension('os-services', | ||||||
|                                                 ServiceController()) |                                                ServiceController(self.ext_mgr)) | ||||||
|  |  | ||||||
|         resources.append(resource) |         resources.append(resource) | ||||||
|         return resources |         return resources | ||||||
|   | |||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | # vim: tabstop=4 shiftwidth=4 softtabstop=4 | ||||||
|  |  | ||||||
|  | # Copyright 2013 OpenStack Foundation. | ||||||
|  | # | ||||||
|  | #    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 sqlalchemy import Column, MetaData, String, Table | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def upgrade(migrate_engine): | ||||||
|  |     meta = MetaData() | ||||||
|  |     meta.bind = migrate_engine | ||||||
|  |     services = Table('services', meta, autoload=True) | ||||||
|  |     reason = Column('disabled_reason', String(255)) | ||||||
|  |     services.create_column(reason) | ||||||
|  |     shadow_services = Table('shadow_services', meta, autoload=True) | ||||||
|  |     shadow_services.create_column(reason.copy()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def downgrade(migrate_engine): | ||||||
|  |     meta = MetaData() | ||||||
|  |     meta.bind = migrate_engine | ||||||
|  |     services = Table('services', meta, autoload=True) | ||||||
|  |     services.drop_column('disabled_reason') | ||||||
|  |     shadow_services = Table('shadow_services', meta, autoload=True) | ||||||
|  |     shadow_services.drop_column('disabled_reason') | ||||||
| @@ -53,6 +53,7 @@ class Service(BASE, NovaBase): | |||||||
|     topic = Column(String(255), nullable=True) |     topic = Column(String(255), nullable=True) | ||||||
|     report_count = Column(Integer, nullable=False, default=0) |     report_count = Column(Integer, nullable=False, default=0) | ||||||
|     disabled = Column(Boolean, default=False) |     disabled = Column(Boolean, default=False) | ||||||
|  |     disabled_reason = Column(String(255)) | ||||||
|  |  | ||||||
|  |  | ||||||
| class ComputeNode(BASE, NovaBase): | class ComputeNode(BASE, NovaBase): | ||||||
|   | |||||||
| @@ -14,8 +14,10 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
| import datetime | import datetime | ||||||
|  | import webob.exc | ||||||
|  |  | ||||||
| from nova.api.openstack.compute.contrib import services | from nova.api.openstack.compute.contrib import services | ||||||
|  | from nova.api.openstack import extensions | ||||||
| from nova import availability_zones | from nova import availability_zones | ||||||
| from nova import context | from nova import context | ||||||
| from nova import db | from nova import db | ||||||
| @@ -33,28 +35,32 @@ fake_services_list = [ | |||||||
|          'disabled': True, |          'disabled': True, | ||||||
|          'topic': 'scheduler', |          'topic': 'scheduler', | ||||||
|          'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2), |          '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', |         {'binary': 'nova-compute', | ||||||
|          'host': 'host1', |          'host': 'host1', | ||||||
|          'id': 2, |          'id': 2, | ||||||
|          'disabled': True, |          'disabled': True, | ||||||
|          'topic': 'compute', |          'topic': 'compute', | ||||||
|          'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5), |          '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', |         {'binary': 'nova-scheduler', | ||||||
|          'host': 'host2', |          'host': 'host2', | ||||||
|          'id': 3, |          'id': 3, | ||||||
|          'disabled': False, |          'disabled': False, | ||||||
|          'topic': 'scheduler', |          'topic': 'scheduler', | ||||||
|          'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34), |          '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', |         {'binary': 'nova-compute', | ||||||
|          'host': 'host2', |          'host': 'host2', | ||||||
|          'id': 4, |          'id': 4, | ||||||
|          'disabled': True, |          'disabled': True, | ||||||
|          'topic': 'compute', |          'topic': 'compute', | ||||||
|          'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38), |          '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) |     service = fake_service_get_by_id(service_id) | ||||||
|     if service is None: |     if service is None: | ||||||
|         raise exception.ServiceNotFound(service_id=service_id) |         raise exception.ServiceNotFound(service_id=service_id) | ||||||
|     else: |  | ||||||
|         {'host': 'host1', 'service': 'nova-compute', |  | ||||||
|          'disabled': values['disabled']} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def fake_utcnow(): | def fake_utcnow(): | ||||||
| @@ -121,8 +124,9 @@ class ServicesTest(test.TestCase): | |||||||
|         super(ServicesTest, self).setUp() |         super(ServicesTest, self).setUp() | ||||||
|  |  | ||||||
|         self.context = context.get_admin_context() |         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", |         self.stubs.Set(self.controller.host_api, "service_get_all", | ||||||
|                        fake_host_api_service_get_all) |                        fake_host_api_service_get_all) | ||||||
|         self.stubs.Set(timeutils, "utcnow", fake_utcnow) |         self.stubs.Set(timeutils, "utcnow", fake_utcnow) | ||||||
| @@ -134,21 +138,30 @@ class ServicesTest(test.TestCase): | |||||||
|         req = FakeRequest() |         req = FakeRequest() | ||||||
|         res_dict = self.controller.index(req) |         res_dict = self.controller.index(req) | ||||||
|  |  | ||||||
|         response = {'services': [{'binary': 'nova-scheduler', |         response = {'services': [ | ||||||
|                     'host': 'host1', 'zone': 'internal', |                     {'binary': 'nova-scheduler', | ||||||
|                     'status': 'disabled', 'state': 'up', |                     'host': 'host1', | ||||||
|  |                     'zone': 'internal', | ||||||
|  |                     'status': 'disabled', | ||||||
|  |                     'state': 'up', | ||||||
|                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)}, |                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)}, | ||||||
|                     {'binary': 'nova-compute', |                     {'binary': 'nova-compute', | ||||||
|                      'host': 'host1', 'zone': 'nova', |                      'host': 'host1', | ||||||
|                      'status': 'disabled', 'state': 'up', |  | ||||||
|                      'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}, |  | ||||||
|                     {'binary': 'nova-scheduler', 'host': 'host2', |  | ||||||
|                      'zone': 'internal', |  | ||||||
|                      'status': 'enabled', 'state': 'down', |  | ||||||
|                      'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34)}, |  | ||||||
|                     {'binary': 'nova-compute', 'host': 'host2', |  | ||||||
|                      'zone': 'nova', |                      'zone': 'nova', | ||||||
|                      'status': 'disabled', 'state': 'down', |                      'status': 'disabled', | ||||||
|  |                      'state': 'up', | ||||||
|  |                      'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}, | ||||||
|  |                     {'binary': 'nova-scheduler', | ||||||
|  |                      'host': 'host2', | ||||||
|  |                      'zone': 'internal', | ||||||
|  |                      'status': 'enabled', | ||||||
|  |                      'state': 'down', | ||||||
|  |                      'updated_at': datetime.datetime(2012, 9, 19, 6, 55, 34)}, | ||||||
|  |                     {'binary': 'nova-compute', | ||||||
|  |                      'host': 'host2', | ||||||
|  |                      'zone': 'nova', | ||||||
|  |                      'status': 'disabled', | ||||||
|  |                      'state': 'down', | ||||||
|                      'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]} |                      'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]} | ||||||
|         self.assertEqual(res_dict, response) |         self.assertEqual(res_dict, response) | ||||||
|  |  | ||||||
| @@ -156,13 +169,18 @@ class ServicesTest(test.TestCase): | |||||||
|         req = FakeRequestWithHost() |         req = FakeRequestWithHost() | ||||||
|         res_dict = self.controller.index(req) |         res_dict = self.controller.index(req) | ||||||
|  |  | ||||||
|         response = {'services': [{'binary': 'nova-scheduler', 'host': 'host1', |         response = {'services': [ | ||||||
|  |                     {'binary': 'nova-scheduler', | ||||||
|  |                     'host': 'host1', | ||||||
|                     'zone': 'internal', |                     'zone': 'internal', | ||||||
|                     'status': 'disabled', 'state': 'up', |                     'status': 'disabled', | ||||||
|  |                     'state': 'up', | ||||||
|                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)}, |                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 2)}, | ||||||
|                    {'binary': 'nova-compute', 'host': 'host1', |                    {'binary': 'nova-compute', | ||||||
|  |                     'host': 'host1', | ||||||
|                     'zone': 'nova', |                     'zone': 'nova', | ||||||
|                     'status': 'disabled', 'state': 'up', |                     'status': 'disabled', | ||||||
|  |                     'state': 'up', | ||||||
|                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]} |                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]} | ||||||
|         self.assertEqual(res_dict, response) |         self.assertEqual(res_dict, response) | ||||||
|  |  | ||||||
| @@ -170,13 +188,18 @@ class ServicesTest(test.TestCase): | |||||||
|         req = FakeRequestWithService() |         req = FakeRequestWithService() | ||||||
|         res_dict = self.controller.index(req) |         res_dict = self.controller.index(req) | ||||||
|  |  | ||||||
|         response = {'services': [{'binary': 'nova-compute', 'host': 'host1', |         response = {'services': [ | ||||||
|  |                     {'binary': 'nova-compute', | ||||||
|  |                     'host': 'host1', | ||||||
|                     'zone': 'nova', |                     'zone': 'nova', | ||||||
|                     'status': 'disabled', 'state': 'up', |                     'status': 'disabled', | ||||||
|  |                     'state': 'up', | ||||||
|                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}, |                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}, | ||||||
|                     {'binary': 'nova-compute', 'host': 'host2', |                     {'binary': 'nova-compute', | ||||||
|  |                      'host': 'host2', | ||||||
|                      'zone': 'nova', |                      'zone': 'nova', | ||||||
|                      'status': 'disabled', 'state': 'down', |                      'status': 'disabled', | ||||||
|  |                      'state': 'down', | ||||||
|                      'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]} |                      'updated_at': datetime.datetime(2012, 9, 18, 8, 3, 38)}]} | ||||||
|         self.assertEqual(res_dict, response) |         self.assertEqual(res_dict, response) | ||||||
|  |  | ||||||
| @@ -184,25 +207,125 @@ class ServicesTest(test.TestCase): | |||||||
|         req = FakeRequestWithHostService() |         req = FakeRequestWithHostService() | ||||||
|         res_dict = self.controller.index(req) |         res_dict = self.controller.index(req) | ||||||
|  |  | ||||||
|         response = {'services': [{'binary': 'nova-compute', 'host': 'host1', |         response = {'services': [ | ||||||
|  |                     {'binary': 'nova-compute', | ||||||
|  |                     'host': 'host1', | ||||||
|                     'zone': 'nova', |                     'zone': 'nova', | ||||||
|                     'status': 'disabled', 'state': 'up', |                     'status': 'disabled', | ||||||
|  |                     'state': 'up', | ||||||
|                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]} |                     'updated_at': datetime.datetime(2012, 10, 29, 13, 42, 5)}]} | ||||||
|         self.assertEqual(res_dict, response) |         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): |     def test_services_enable(self): | ||||||
|         body = {'host': 'host1', 'binary': 'nova-compute'} |         body = {'host': 'host1', 'binary': 'nova-compute'} | ||||||
|         req = fakes.HTTPRequest.blank('/v2/fake/os-services/enable') |         req = fakes.HTTPRequest.blank('/v2/fake/os-services/enable') | ||||||
|         res_dict = self.controller.update(req, "enable", body) |         res_dict = self.controller.update(req, "enable", body) | ||||||
|  |  | ||||||
|         self.assertEqual(res_dict['service']['status'], 'enabled') |         self.assertEqual(res_dict['service']['status'], 'enabled') | ||||||
|  |         self.assertFalse('disabled_reason' in res_dict['service']) | ||||||
|     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') |  | ||||||
|  |  | ||||||
|     # This test is just to verify that the servicegroup API gets used when |     # This test is just to verify that the servicegroup API gets used when | ||||||
|     # calling this API. |     # calling this API. | ||||||
| @@ -213,3 +336,44 @@ class ServicesTest(test.TestCase): | |||||||
|         self.stubs.Set(db_driver.DbDriver, 'is_up', dummy_is_up) |         self.stubs.Set(db_driver.DbDriver, 'is_up', dummy_is_up) | ||||||
|         req = FakeRequestWithHostService() |         req = FakeRequestWithHostService() | ||||||
|         self.assertRaises(KeyError, self.controller.index, req) |         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)) | ||||||
|   | |||||||
| @@ -784,6 +784,7 @@ class CellsTargetedMethodsTestCase(test.TestCase): | |||||||
|         result = response.value_or_raise() |         result = response.value_or_raise() | ||||||
|         result.pop('created_at', None) |         result.pop('created_at', None) | ||||||
|         result.pop('updated_at', None) |         result.pop('updated_at', None) | ||||||
|  |         result.pop('disabled_reason', None) | ||||||
|         expected_result = dict( |         expected_result = dict( | ||||||
|             deleted=0, deleted_at=None, |             deleted=0, deleted_at=None, | ||||||
|             binary=fake_service['binary'], |             binary=fake_service['binary'], | ||||||
|   | |||||||
| @@ -1626,6 +1626,16 @@ class TestNovaMigrations(BaseMigrationTestCase, CommonTestsMixIn): | |||||||
|         # check that groups does not exist |         # check that groups does not exist | ||||||
|         self._check_no_group_instance_tables(engine) |         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): | class TestBaremetalMigrations(BaseMigrationTestCase, CommonTestsMixIn): | ||||||
|     """Test sqlalchemy-migrate migrations.""" |     """Test sqlalchemy-migrate migrations.""" | ||||||
|   | |||||||
| @@ -360,6 +360,14 @@ | |||||||
|             "namespace": "http://docs.openstack.org/compute/ext/services/api/v2", |             "namespace": "http://docs.openstack.org/compute/ext/services/api/v2", | ||||||
|             "updated": "%(timestamp)s" |             "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", |             "alias": "os-fping", | ||||||
|             "description": "%(text)s", |             "description": "%(text)s", | ||||||
|   | |||||||
| @@ -135,6 +135,9 @@ | |||||||
|   <extension alias="os-services" name="Services" namespace="http://docs.openstack.org/compute/ext/services/api/v2" updated="%(timestamp)s"> |   <extension alias="os-services" name="Services" namespace="http://docs.openstack.org/compute/ext/services/api/v2" updated="%(timestamp)s"> | ||||||
|     <description>%(text)s</description> |     <description>%(text)s</description> | ||||||
|   </extension> |   </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"> |   <extension alias="os-fping" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/fping/api/v1.1" name="Fping"> | ||||||
|     <description>%(text)s</description> |     <description>%(text)s</description> | ||||||
|   </extension> |   </extension> | ||||||
|   | |||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | { | ||||||
|  |     "host": "%(host)s", | ||||||
|  |     "binary": "%(binary)s", | ||||||
|  |     "disabled_reason": "%(disabled_reason)s" | ||||||
|  | } | ||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | <?xml version='1.0' encoding='UTF-8'?> | ||||||
|  | <service host="%(host)s" binary="%(binary)s" disabled_reason="%(disabled_reason)s"/> | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | { | ||||||
|  |     "service": { | ||||||
|  |         "binary": "%(binary)s", | ||||||
|  |         "host": "%(host)s", | ||||||
|  |         "disabled_reason": "%(disabled_reason)s", | ||||||
|  |         "status": "disabled" | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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"/> | ||||||
| @@ -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" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
| @@ -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> | ||||||
| @@ -30,6 +30,7 @@ from oslo.config import cfg | |||||||
| from nova.api.metadata import password | from nova.api.metadata import password | ||||||
| from nova.api.openstack.compute.contrib import coverage_ext | from nova.api.openstack.compute.contrib import coverage_ext | ||||||
| from nova.api.openstack.compute.contrib import fping | 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. | # Import extensions to pull in osapi_compute_extension CONF option used below. | ||||||
| from nova.cells import state | from nova.cells import state | ||||||
| from nova.cloudpipe import pipelib | from nova.cloudpipe import pipelib | ||||||
| @@ -1965,6 +1966,9 @@ class ServicesJsonTest(ApiSampleTestBase): | |||||||
|         super(ServicesJsonTest, self).tearDown() |         super(ServicesJsonTest, self).tearDown() | ||||||
|         timeutils.clear_time_override() |         timeutils.clear_time_override() | ||||||
|  |  | ||||||
|  |     def fake_load(self, *args): | ||||||
|  |         return True | ||||||
|  |  | ||||||
|     def test_services_list(self): |     def test_services_list(self): | ||||||
|         """Return a list of all agent builds.""" |         """Return a list of all agent builds.""" | ||||||
|         response = self._do_get('os-services') |         response = self._do_get('os-services') | ||||||
| @@ -1996,11 +2000,55 @@ class ServicesJsonTest(ApiSampleTestBase): | |||||||
|                 "binary": "nova-compute"} |                 "binary": "nova-compute"} | ||||||
|         self._verify_response('service-disable-put-resp', subs, response, 200) |         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): | class ServicesXmlTest(ServicesJsonTest): | ||||||
|     ctype = 'xml' |     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): | class SimpleTenantUsageSampleJsonTest(ServersSampleBase): | ||||||
|     extension_name = ("nova.api.openstack.compute.contrib.simple_tenant_usage." |     extension_name = ("nova.api.openstack.compute.contrib.simple_tenant_usage." | ||||||
|                       "Simple_tenant_usage") |                       "Simple_tenant_usage") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user