summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.json8
-rw-r--r--doc/api_samples/all_extensions/extensions-get-resp.xml3
-rw-r--r--doc/api_samples/os-services/service-disable-log-put-req.json5
-rw-r--r--doc/api_samples/os-services/service-disable-log-put-req.xml2
-rw-r--r--doc/api_samples/os-services/service-disable-log-put-resp.json8
-rw-r--r--doc/api_samples/os-services/service-disable-log-put-resp.xml2
-rw-r--r--doc/api_samples/os-services/service-enable-put-req.json2
-rw-r--r--doc/api_samples/os-services/services-get-resp.json40
-rw-r--r--doc/api_samples/os-services/services-get-resp.xml6
-rw-r--r--nova/api/openstack/compute/contrib/extended_services.py11
-rw-r--r--nova/api/openstack/compute/contrib/services.py107
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/188_add_reason_column_to_service.py36
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_services.py234
-rw-r--r--nova/tests/cells/test_cells_messaging.py1
-rw-r--r--nova/tests/db/test_migrations.py10
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl3
-rw-r--r--nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.json.tpl5
-rw-r--r--nova/tests/integrated/api_samples/os-services/service-disable-log-put-req.xml.tpl2
-rw-r--r--nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.json.tpl8
-rw-r--r--nova/tests/integrated/api_samples/os-services/service-disable-log-put-resp.xml.tpl2
-rw-r--r--nova/tests/integrated/api_samples/os-services/services-get-resp.json.tpl40
-rw-r--r--nova/tests/integrated/api_samples/os-services/services-get-resp.xml.tpl7
-rw-r--r--nova/tests/integrated/test_api_samples.py48
25 files changed, 537 insertions, 62 deletions
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json
index 3752aedf5..09ede77dc 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.json
+++ b/doc/api_samples/all_extensions/extensions-get-resp.json
@@ -497,6 +497,14 @@
"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.",
"links": [],
diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml
index 97cfd4abd..1c6a9a58b 100644
--- a/doc/api_samples/all_extensions/extensions-get-resp.xml
+++ b/doc/api_samples/all_extensions/extensions-get-resp.xml
@@ -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>
diff --git a/doc/api_samples/os-services/service-disable-log-put-req.json b/doc/api_samples/os-services/service-disable-log-put-req.json
new file mode 100644
index 000000000..6ccc5524e
--- /dev/null
+++ b/doc/api_samples/os-services/service-disable-log-put-req.json
@@ -0,0 +1,5 @@
+{
+ "host": "host1",
+ "binary": "nova-compute",
+ "disabled_reason": "test2"
+}
diff --git a/doc/api_samples/os-services/service-disable-log-put-req.xml b/doc/api_samples/os-services/service-disable-log-put-req.xml
new file mode 100644
index 000000000..f8eec8173
--- /dev/null
+++ b/doc/api_samples/os-services/service-disable-log-put-req.xml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<service host="host1" binary="nova-compute" disabled_reason="test2"/>
diff --git a/doc/api_samples/os-services/service-disable-log-put-resp.json b/doc/api_samples/os-services/service-disable-log-put-resp.json
new file mode 100644
index 000000000..c82bc54c3
--- /dev/null
+++ b/doc/api_samples/os-services/service-disable-log-put-resp.json
@@ -0,0 +1,8 @@
+{
+ "service": {
+ "binary": "nova-compute",
+ "host": "host1",
+ "disabled_reason": "test2",
+ "status": "disabled"
+ }
+}
diff --git a/doc/api_samples/os-services/service-disable-log-put-resp.xml b/doc/api_samples/os-services/service-disable-log-put-resp.xml
new file mode 100644
index 000000000..1ddf273b1
--- /dev/null
+++ b/doc/api_samples/os-services/service-disable-log-put-resp.xml
@@ -0,0 +1,2 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<service host="host1" binary="nova-compute" status="disabled" disabled_reason="test2" />
diff --git a/doc/api_samples/os-services/service-enable-put-req.json b/doc/api_samples/os-services/service-enable-put-req.json
index ffe896999..f96d34536 100644
--- a/doc/api_samples/os-services/service-enable-put-req.json
+++ b/doc/api_samples/os-services/service-enable-put-req.json
@@ -1,4 +1,4 @@
{
"host": "host1",
- "service": "nova-compute"
+ "binary": "nova-compute"
}
diff --git a/doc/api_samples/os-services/services-get-resp.json b/doc/api_samples/os-services/services-get-resp.json
new file mode 100644
index 000000000..4c873d636
--- /dev/null
+++ b/doc/api_samples/os-services/services-get-resp.json
@@ -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"
+ }
+ ]
+}
diff --git a/doc/api_samples/os-services/services-get-resp.xml b/doc/api_samples/os-services/services-get-resp.xml
new file mode 100644
index 000000000..997fc3945
--- /dev/null
+++ b/doc/api_samples/os-services/services-get-resp.xml
@@ -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>
diff --git a/nova/api/openstack/compute/contrib/extended_services.py b/nova/api/openstack/compute/contrib/extended_services.py
new file mode 100644
index 000000000..f9f1a4d86
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/extended_services.py
@@ -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"
diff --git a/nova/api/openstack/compute/contrib/services.py b/nova/api/openstack/compute/contrib/services.py
index bc5f60b64..3a637010a 100644
--- a/nova/api/openstack/compute/contrib/services.py
+++ b/nova/api/openstack/compute/contrib/services.py
@@ -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
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/188_add_reason_column_to_service.py b/nova/db/sqlalchemy/migrate_repo/versions/188_add_reason_column_to_service.py
new file mode 100644
index 000000000..ed87bcfeb
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/188_add_reason_column_to_service.py
@@ -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')
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 28fe36a0d..d12e47246 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -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):
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")