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:
parent
d7f898eab9
commit
c741e862fd
@ -496,6 +496,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/services/api/v2",
|
||||
"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",
|
||||
"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">
|
||||
<description>Services support.</description>
|
||||
</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">
|
||||
<description>Simple tenant usage extension.</description>
|
||||
</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",
|
||||
"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 exception
|
||||
from nova import servicegroup
|
||||
from nova import utils
|
||||
|
||||
authorize = extensions.extension_authorizer('compute', 'services')
|
||||
CONF = cfg.CONF
|
||||
@ -39,6 +40,7 @@ class ServicesIndexTemplate(xmlutil.TemplateBuilder):
|
||||
elem.set('status')
|
||||
elem.set('state')
|
||||
elem.set('updated_at')
|
||||
elem.set('disabled_reason')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
@ -49,6 +51,7 @@ class ServiceUpdateTemplate(xmlutil.TemplateBuilder):
|
||||
root.set('host')
|
||||
root.set('binary')
|
||||
root.set('status')
|
||||
root.set('disabled_reason')
|
||||
|
||||
return xmlutil.MasterTemplate(root, 1)
|
||||
|
||||
@ -62,21 +65,20 @@ class ServiceUpdateDeserializer(wsgi.XMLDeserializer):
|
||||
return service
|
||||
service['host'] = service_node.getAttribute('host')
|
||||
service['binary'] = service_node.getAttribute('binary')
|
||||
service['disabled_reason'] = service_node.getAttribute(
|
||||
'disabled_reason')
|
||||
|
||||
return dict(body=service)
|
||||
|
||||
|
||||
class ServiceController(object):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, ext_mgr=None, *args, **kwargs):
|
||||
self.host_api = compute.HostAPI()
|
||||
self.servicegroup_api = servicegroup.API()
|
||||
self.ext_mgr = ext_mgr
|
||||
|
||||
@wsgi.serializers(xml=ServicesIndexTemplate)
|
||||
def index(self, req):
|
||||
"""
|
||||
Return a list of all running services. Filter by host & service name.
|
||||
"""
|
||||
def _get_services(self, req):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
services = self.host_api.service_get_all(
|
||||
@ -93,18 +95,49 @@ class ServiceController(object):
|
||||
if binary:
|
||||
services = [s for s in services if s['binary'] == binary]
|
||||
|
||||
return services
|
||||
|
||||
def _get_service_detail(self, svc, detailed):
|
||||
alive = self.servicegroup_api.service_is_up(svc)
|
||||
state = (alive and "up") or "down"
|
||||
active = 'enabled'
|
||||
if svc['disabled']:
|
||||
active = 'disabled'
|
||||
service_detail = {'binary': svc['binary'], 'host': svc['host'],
|
||||
'zone': svc['availability_zone'],
|
||||
'status': active, 'state': state,
|
||||
'updated_at': svc['updated_at']}
|
||||
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:
|
||||
alive = self.servicegroup_api.service_is_up(svc)
|
||||
art = (alive and "up") or "down"
|
||||
active = 'enabled'
|
||||
if svc['disabled']:
|
||||
active = 'disabled'
|
||||
svcs.append({"binary": svc['binary'], 'host': svc['host'],
|
||||
'zone': svc['availability_zone'],
|
||||
'status': active, 'state': art,
|
||||
'updated_at': svc['updated_at']})
|
||||
return {'services': svcs}
|
||||
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.serializers(xml=ServiceUpdateTemplate)
|
||||
@ -113,28 +146,49 @@ class ServiceController(object):
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
|
||||
ext_loaded = self.ext_mgr.is_loaded('os-extended-services')
|
||||
if id == "enable":
|
||||
disabled = False
|
||||
elif id == "disable":
|
||||
status = "enabled"
|
||||
elif (id == "disable" or
|
||||
(id == "disable-log-reason" and ext_loaded)):
|
||||
disabled = True
|
||||
status = "disabled"
|
||||
else:
|
||||
raise webob.exc.HTTPNotFound(_("Unknown action"))
|
||||
|
||||
status = id + 'd'
|
||||
|
||||
raise webob.exc.HTTPNotFound("Unknown action")
|
||||
try:
|
||||
host = body['host']
|
||||
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):
|
||||
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:
|
||||
svc = self.host_api.service_update(context, host, binary,
|
||||
{'disabled': disabled})
|
||||
except exception.ServiceNotFound as exc:
|
||||
status_detail)
|
||||
except exception.ServiceNotFound:
|
||||
raise webob.exc.HTTPNotFound(_("Unknown service"))
|
||||
|
||||
return {'service': {'host': host, 'binary': binary, 'status': status}}
|
||||
return ret_value
|
||||
|
||||
|
||||
class Services(extensions.ExtensionDescriptor):
|
||||
@ -148,6 +202,7 @@ class Services(extensions.ExtensionDescriptor):
|
||||
def get_resources(self):
|
||||
resources = []
|
||||
resource = extensions.ResourceExtension('os-services',
|
||||
ServiceController())
|
||||
ServiceController(self.ext_mgr))
|
||||
|
||||
resources.append(resource)
|
||||
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)
|
||||
report_count = Column(Integer, nullable=False, default=0)
|
||||
disabled = Column(Boolean, default=False)
|
||||
disabled_reason = Column(String(255))
|
||||
|
||||
|
||||
class ComputeNode(BASE, NovaBase):
|
||||
|
@ -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',
|
||||
'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',
|
||||
'host': 'host1',
|
||||
'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)}]}
|
||||
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))
|
||||
|
@ -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'],
|
||||
|
@ -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."""
|
||||
|
@ -360,6 +360,14 @@
|
||||
"namespace": "http://docs.openstack.org/compute/ext/services/api/v2",
|
||||
"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",
|
||||
|
@ -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>
|
||||
|
@ -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.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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user