From e3132d29f40333e66500a454ac8f68b652d4ef12 Mon Sep 17 00:00:00 2001 From: John Griffith Date: Tue, 18 Dec 2012 23:02:32 +0000 Subject: [PATCH] Add service mgmt extension. Add capability to list, enable and disable cinder services on a node. Change-Id: I6728fee46d7b15a009bc6c329414ede8c01852b4 --- cinder/api/contrib/services.py | 139 ++++++++++++++ cinder/tests/api/contrib/test_services.py | 216 ++++++++++++++++++++++ cinder/tests/api/v2/test_volumes.py | 1 - etc/cinder/policy.json | 4 +- 4 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 cinder/api/contrib/services.py create mode 100644 cinder/tests/api/contrib/test_services.py diff --git a/cinder/api/contrib/services.py b/cinder/api/contrib/services.py new file mode 100644 index 000000000..ed4e4d0e6 --- /dev/null +++ b/cinder/api/contrib/services.py @@ -0,0 +1,139 @@ +# 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 cinder.api import extensions +from cinder.api.openstack import wsgi +from cinder.api import xmlutil +from cinder import db +from cinder import exception +from cinder.openstack.common import log as logging +from cinder.openstack.common import timeutils +from cinder import utils + + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('volume', 'services') + + +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['cinder.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)) + 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['cinder.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/volume/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/cinder/tests/api/contrib/test_services.py b/cinder/tests/api/contrib/test_services.py new file mode 100644 index 000000000..107c0f84a --- /dev/null +++ b/cinder/tests/api/contrib/test_services.py @@ -0,0 +1,216 @@ +# 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 cinder.api.contrib import services +from cinder import context +from cinder import db +from cinder import exception +from cinder.openstack.common import timeutils +from cinder import policy +from cinder import test +from cinder.tests.api import fakes +from datetime import datetime + + +fake_services_list = [{'binary': 'cinder-scheduler', + 'host': 'host1', + 'availability_zone': 'cinder', + 'id': 1, + 'disabled': True, + 'updated_at': datetime(2012, 10, 29, 13, 42, 2), + 'created_at': datetime(2012, 9, 18, 2, 46, 27)}, + {'binary': 'cinder-volume', + 'host': 'host1', + 'availability_zone': 'cinder', + 'id': 2, + 'disabled': True, + 'updated_at': datetime(2012, 10, 29, 13, 42, 5), + 'created_at': datetime(2012, 9, 18, 2, 46, 27)}, + {'binary': 'cinder-scheduler', + 'host': 'host2', + 'availability_zone': 'cinder', + 'id': 3, + 'disabled': False, + 'updated_at': datetime(2012, 9, 19, 6, 55, 34), + 'created_at': datetime(2012, 9, 18, 2, 46, 28)}, + {'binary': 'cinder-volume', + 'host': 'host2', + 'availability_zone': 'cinder', + '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 = {"cinder.context": context.get_admin_context()} + GET = {} + + +class FakeRequestWithSevice(object): + environ = {"cinder.context": context.get_admin_context()} + GET = {"service": "cinder-volume"} + + +class FakeRequestWithHost(object): + environ = {"cinder.context": context.get_admin_context()} + GET = {"host": "host1"} + + +class FakeRequestWithHostService(object): + environ = {"cinder.context": context.get_admin_context()} + GET = {"host": "host1", "service": "cinder-volume"} + + +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': 'cinder-volume', + 'disabled': values['disabled']} + + +def fake_policy_enforce(context, action, target): + pass + + +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.stubs.Set(policy, "enforce", fake_policy_enforce) + + 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': 'cinder-scheduler', + 'host': 'host1', 'zone': 'cinder', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 2)}, + {'binary': 'cinder-volume', + 'host': 'host1', 'zone': 'cinder', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, 29, 13, 42, 5)}, + {'binary': 'cinder-scheduler', 'host': 'host2', + 'zone': 'cinder', + 'status': 'enabled', 'state': 'up', + 'updated_at': datetime(2012, 9, 19, 6, 55, 34)}, + {'binary': 'cinder-volume', 'host': 'host2', + 'zone': 'cinder', + 'status': 'disabled', 'state': 'up', + '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': 'cinder-scheduler', + 'host': 'host1', + 'zone': 'cinder', + 'status': 'disabled', 'state': 'up', + 'updated_at': datetime(2012, 10, + 29, 13, 42, 2)}, + {'binary': 'cinder-volume', 'host': 'host1', + 'zone': 'cinder', + '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': 'cinder-volume', + 'host': 'host1', + 'zone': 'cinder', + 'status': 'disabled', + 'state': 'up', + 'updated_at': datetime(2012, 10, 29, + 13, 42, 5)}, + {'binary': 'cinder-volume', + 'host': 'host2', + 'zone': 'cinder', + 'status': 'disabled', + 'state': 'up', + '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': 'cinder-volume', + 'host': 'host1', + 'zone': 'cinder', + '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': 'cinder-volume'} + req = fakes.HTTPRequest.blank('/v1/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('/v1/fake/os-services/disable') + body = {'host': 'host1', 'service': 'cinder-volume'} + res_dict = self.controller.update(req, "disable", body) + + self.assertEqual(res_dict['disabled'], True) diff --git a/cinder/tests/api/v2/test_volumes.py b/cinder/tests/api/v2/test_volumes.py index 0e22fcb44..5544d8631 100644 --- a/cinder/tests/api/v2/test_volumes.py +++ b/cinder/tests/api/v2/test_volumes.py @@ -408,7 +408,6 @@ class VolumeApiTest(test.TestCase): self.assertEqual(len(resp['volumes']), 3) # filter on name req = fakes.HTTPRequest.blank('/v2/volumes?name=vol2') - #import pdb; pdb.set_trace() resp = self.controller.index(req) self.assertEqual(len(resp['volumes']), 1) self.assertEqual(resp['volumes'][0]['name'], 'vol2') diff --git a/etc/cinder/policy.json b/etc/cinder/policy.json index 9b088780a..9f4a3897d 100644 --- a/etc/cinder/policy.json +++ b/etc/cinder/policy.json @@ -27,5 +27,7 @@ "volume_extension:volume_host_attribute": [["rule:admin_api"]], "volume_extension:volume_tenant_attribute": [["rule:admin_api"]], - "volume_extension:hosts": [["rule:admin_api"]] + "volume_extension:hosts": [["rule:admin_api"]], + "volume_extension:services": [["rule:admin_api"]], + "volume:services": [["rule:admin_api"]] } -- 2.45.2