diff options
| author | ivan-zhu <bozhu@linux.vnet.ibm.com> | 2012-11-01 23:04:43 +0800 |
|---|---|---|
| committer | ivan-zhu <bozhu@linux.vnet.ibm.com> | 2012-11-10 23:30:47 +0800 |
| commit | 7cee40a47e3bb838fb5d22174e2774347fdb10d2 (patch) | |
| tree | 5a2e63087dea2fbf3757d2a5f3ada068169b8c68 | |
| parent | d9c95e4c8bb0a2d9d7f73600d82bfd32318c37a6 (diff) | |
| download | nova-7cee40a47e3bb838fb5d22174e2774347fdb10d2.tar.gz nova-7cee40a47e3bb838fb5d22174e2774347fdb10d2.tar.xz nova-7cee40a47e3bb838fb5d22174e2774347fdb10d2.zip | |
Add REST API support for list/enable/disable nova services
Implements one workitem of blueprint apis-for-nova-manage
This adds an extension that provides REST API for list/enable/
disable nova service. The interface ia accessed via
GET /v2/{tenant_id}/os-services
PUT /v2/{tenant_id}/os-services/enable
PUT /v2/{tenant_id}/os-services/disable
And the command:nova host-describe have implemented the functionality
of nova-manage service describe_resource. So we needn't add a REST
API for it.
DocImpact
Change-Id: I030a7e00b878d7931456e7e323db37b7c47fce48
7 files changed, 353 insertions, 0 deletions
diff --git a/etc/nova/policy.json b/etc/nova/policy.json index f77f733c6..c3b9dd8cd 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -58,6 +58,7 @@ "compute_extension:rescue": "", "compute_extension:security_groups": "", "compute_extension:server_diagnostics": "rule:admin_api", + "compute_extension:services": "rule:admin_api", "compute_extension:simple_tenant_usage:show": "rule:admin_or_owner", "compute_extension:simple_tenant_usage:list": "rule:admin_api", "compute_extension:users": "rule:admin_api", diff --git a/nova/api/openstack/compute/contrib/services.py b/nova/api/openstack/compute/contrib/services.py new file mode 100644 index 000000000..3da00a8c8 --- /dev/null +++ b/nova/api/openstack/compute/contrib/services.py @@ -0,0 +1,141 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 IBM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import webob.exc + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import db +from nova import exception +from nova import flags +from nova.openstack.common import log as logging +from nova.openstack.common import timeutils +from nova import utils + + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', 'services') +FLAGS = flags.FLAGS + + +class ServicesIndexTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('services') + elem = xmlutil.SubTemplateElement(root, 'service', selector='services') + elem.set('binary') + elem.set('host') + elem.set('zone') + elem.set('status') + elem.set('state') + elem.set('update_at') + + return xmlutil.MasterTemplate(root, 1) + + +class ServicesUpdateTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('host') + root.set('host') + root.set('service') + root.set('disabled') + + return xmlutil.MasterTemplate(root, 1) + + +class ServiceController(object): + @wsgi.serializers(xml=ServicesIndexTemplate) + def index(self, req): + """ + Return a list of all running services. Filter by host & service name. + """ + context = req.environ['nova.context'] + authorize(context) + now = timeutils.utcnow() + services = db.service_get_all(context) + + host = '' + if 'host' in req.GET: + host = req.GET['host'] + service = '' + if 'service' in req.GET: + service = req.GET['service'] + if host: + services = [s for s in services if s['host'] == host] + if service: + services = [s for s in services if s['binary'] == service] + + svcs = [] + for svc in services: + delta = now - (svc['updated_at'] or svc['created_at']) + alive = abs(utils.total_seconds(delta)) <= FLAGS.service_down_time + 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} + + @wsgi.serializers(xml=ServicesUpdateTemplate) + def update(self, req, id, body): + """Enable/Disable scheduling for a service""" + context = req.environ['nova.context'] + authorize(context) + + if id == "enable": + disabled = False + elif id == "disable": + disabled = True + else: + raise webob.exc.HTTPNotFound("Unknown action") + + try: + host = body['host'] + service = body['service'] + except (TypeError, KeyError): + raise webob.exc.HTTPUnprocessableEntity() + + try: + svc = db.service_get_by_args(context, host, service) + if not svc: + raise webob.exc.HTTPNotFound('Unknown service') + + db.service_update(context, svc['id'], {'disabled': disabled}) + except exception.ServiceNotFound: + raise webob.exc.HTTPNotFound("service not found") + + return {'host': host, 'service': service, 'disabled': disabled} + + +class Services(extensions.ExtensionDescriptor): + """Services support""" + + name = "Services" + alias = "os-services" + namespace = "http://docs.openstack.org/compute/ext/services/api/v2" + updated = "2012-10-28T00:00:00-00:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('os-services', + ServiceController()) + resources.append(resource) + return resources diff --git a/nova/tests/api/openstack/compute/contrib/test_services.py b/nova/tests/api/openstack/compute/contrib/test_services.py new file mode 100644 index 000000000..24f169d98 --- /dev/null +++ b/nova/tests/api/openstack/compute/contrib/test_services.py @@ -0,0 +1,198 @@ +# Copyright 2012 IBM +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from datetime import datetime +from nova.api.openstack.compute.contrib import services +from nova import context +from nova import db +from nova import exception +from nova.openstack.common import timeutils +from nova import test +from nova.tests.api.openstack import fakes + + +fake_services_list = [{'binary': 'nova-scheduler', + 'host': 'host1', + 'availability_zone': 'nova', + 'id': 1, + 'disabled': True, + 'updated_at': datetime(2012, 10, 29, 13, 42, 2), + 'created_at': datetime(2012, 9, 18, 2, 46, 27)}, + {'binary': 'nova-compute', + 'host': 'host1', + 'availability_zone': 'nova', + 'id': 2, + 'disabled': True, + 'updated_at': datetime(2012, 10, 29, 13, 42, 5), + 'created_at': datetime(2012, 9, 18, 2, 46, 27)}, + {'binary': 'nova-scheduler', + 'host': 'host2', + 'availability_zone': 'nova', + 'id': 3, + 'disabled': False, + 'updated_at': datetime(2012, 9, 19, 6, 55, 34), + 'created_at': datetime(2012, 9, 18, 2, 46, 28)}, + {'binary': 'nova-compute', + 'host': 'host2', + 'availability_zone': 'nova', + 'id': 4, + 'disabled': True, + 'updated_at': datetime(2012, 9, 18, 8, 3, 38), + 'created_at': datetime(2012, 9, 18, 2, 46, 28)}, + ] + + +class FakeRequest(object): + environ = {"nova.context": context.get_admin_context()} + GET = {} + + +class FakeRequestWithSevice(object): + environ = {"nova.context": context.get_admin_context()} + GET = {"service": "nova-compute"} + + +class FakeRequestWithHost(object): + environ = {"nova.context": context.get_admin_context()} + GET = {"host": "host1"} + + +class FakeRequestWithHostService(object): + environ = {"nova.context": context.get_admin_context()} + GET = {"host": "host1", "service": "nova-compute"} + + +def fake_servcie_get_all(context): + return fake_services_list + + +def fake_service_get_by_host_binary(context, host, binary): + for service in fake_services_list: + if service['host'] == host and service['binary'] == binary: + return service + return None + + +def fake_service_get_by_id(value): + for service in fake_services_list: + if service['id'] == value: + return service + return None + + +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(): + return datetime(2012, 10, 29, 13, 42, 11) + + +class ServicesTest(test.TestCase): + + def setUp(self): + super(ServicesTest, self).setUp() + + self.stubs.Set(db, "service_get_all", fake_servcie_get_all) + self.stubs.Set(timeutils, "utcnow", fake_utcnow) + self.stubs.Set(db, "service_get_by_args", + fake_service_get_by_host_binary) + self.stubs.Set(db, "service_update", fake_service_update) + + self.context = context.get_admin_context() + self.controller = services.ServiceController() + + def tearDown(self): + super(ServicesTest, self).tearDown() + + def test_services_list(self): + req = FakeRequest() + res_dict = self.controller.index(req) + + response = {'services': [{'binary': 'nova-scheduler', + 'host': 'host1', 'zone': 'nova', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 2)}, + {'binary': 'nova-compute', + 'host': 'host1', 'zone': 'nova', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}, + {'binary': 'nova-scheduler', 'host': 'host2', + 'zone': 'nova', + 'status': 'enabled', 'state': 'down', + 'updated_at': datetime(2012, 9, 19, 6, 55, 34)}, + {'binary': 'nova-compute', 'host': 'host2', + 'zone': 'nova', + 'status': 'disabled', 'state': 'down', + 'updated_at': datetime(2012, 9, 18, 8, 3, 38)}]} + self.assertEqual(res_dict, response) + + def test_services_list_with_host(self): + req = FakeRequestWithHost() + res_dict = self.controller.index(req) + + response = {'services': [{'binary': 'nova-scheduler', 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 2)}, + {'binary': 'nova-compute', 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}]} + self.assertEqual(res_dict, response) + + def test_services_list_with_service(self): + req = FakeRequestWithSevice() + res_dict = self.controller.index(req) + + response = {'services': [{'binary': 'nova-compute', 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}, + {'binary': 'nova-compute', 'host': 'host2', + 'zone': 'nova', + 'status': 'disabled', 'state': 'down', + 'updated_at': datetime(2012, 9, 18, 8, 3, 38)}]} + self.assertEqual(res_dict, response) + + def test_services_list_with_host_service(self): + req = FakeRequestWithHostService() + res_dict = self.controller.index(req) + + response = {'services': [{'binary': 'nova-compute', 'host': 'host1', + 'zone': 'nova', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}]} + self.assertEqual(res_dict, response) + + def test_services_enable(self): + body = {'host': 'host1', 'service': 'nova-compute'} + req = fakes.HTTPRequest.blank('/v2/fake/os-services/enable') + res_dict = self.controller.update(req, "enable", body) + + self.assertEqual(res_dict['disabled'], False) + + def test_services_disable(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-services/disable') + body = {'host': 'host1', 'service': 'nova-compute'} + res_dict = self.controller.update(req, "disable", body) + + self.assertEqual(res_dict['disabled'], True) diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index ef2f4eec4..41c28e540 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -189,6 +189,7 @@ class ExtensionControllerTest(ExtensionTestCase): "SecurityGroups", "ServerDiagnostics", "ServerStartStop", + "Services", "SimpleTenantUsage", "UsedLimits", "UserData", 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 f566a5020..0086c213f 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 @@ -209,6 +209,14 @@ "updated": "%(timestamp)s" }, { + "alias": "os-services", + "description": "%(text)s", + "links": [], + "name": "Services", + "namespace": "http://docs.openstack.org/compute/ext/services/api/v2", + "updated": "%(timestamp)s" + }, + { "alias": "os-hypervisors", "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 20e650d7c..c2c856d23 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 @@ -78,6 +78,9 @@ <extension alias="os-hosts" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/hosts/api/v1.1" name="Hosts"> <description>%(text)s</description> </extension> + <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-hypervisors" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/hypervisors/api/v1.1" name="Hypervisors"> <description>%(text)s</description> </extension> diff --git a/nova/tests/policy.json b/nova/tests/policy.json index efe2724ad..bf94d4e49 100644 --- a/nova/tests/policy.json +++ b/nova/tests/policy.json @@ -117,6 +117,7 @@ "compute_extension:rescue": "", "compute_extension:security_groups": "", "compute_extension:server_diagnostics": "", + "compute_extension:services": "", "compute_extension:simple_tenant_usage:show": "", "compute_extension:simple_tenant_usage:list": "", "compute_extension:users": "", |
