]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add service mgmt extension.
authorJohn Griffith <john.griffith@solidfire.com>
Tue, 18 Dec 2012 23:02:32 +0000 (23:02 +0000)
committerJohn Griffith <john.griffith@solidfire.com>
Wed, 19 Dec 2012 23:39:06 +0000 (16:39 -0700)
Add capability to list, enable and disable cinder services on a node.

Change-Id: I6728fee46d7b15a009bc6c329414ede8c01852b4

cinder/api/contrib/services.py [new file with mode: 0644]
cinder/tests/api/contrib/test_services.py [new file with mode: 0644]
cinder/tests/api/v2/test_volumes.py
etc/cinder/policy.json

diff --git a/cinder/api/contrib/services.py b/cinder/api/contrib/services.py
new file mode 100644 (file)
index 0000000..ed4e4d0
--- /dev/null
@@ -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 (file)
index 0000000..107c0f8
--- /dev/null
@@ -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)
index 0e22fcb4491373ada623fa612a3008563214355d..5544d8631603aa85985eba85f573bdb58dc0367c 100644 (file)
@@ -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')
index 9b088780a5c1096f5b3b791d62aaaa1f00d71047..9f4a3897d38a896ee754c4d5bb1db03c0fe44d56 100644 (file)
@@ -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"]]
 }