]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Implement scheduler hints for API v2
authorNikolaj Starodubtsev <nstarodubtsev@mirantis.com>
Thu, 18 Apr 2013 12:42:35 +0000 (16:42 +0400)
committerNikolaj Starodubtsev <nstarodubtsev@mirantis.com>
Thu, 23 May 2013 12:05:37 +0000 (16:05 +0400)
We've done this implementation because we need to use scheduler hint in
cinder with some specific filters. So, most part of code have been
imported from nova.

bp scheduler-hints
docimpact

Change-Id: I4c8a78ade4ff668d79e7aa6d0d358029754e3d90

cinder/api/contrib/scheduler_hints.py [new file with mode: 0644]
cinder/api/openstack/wsgi.py
cinder/api/v2/volumes.py
cinder/tests/api/contrib/test_scheduler_hints.py [new file with mode: 0644]
cinder/volume/api.py

diff --git a/cinder/api/contrib/scheduler_hints.py b/cinder/api/contrib/scheduler_hints.py
new file mode 100644 (file)
index 0000000..17838e5
--- /dev/null
@@ -0,0 +1,63 @@
+# 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
+
+import webob.exc
+
+from cinder.api import extensions
+from cinder.api.openstack import wsgi
+from cinder.api.v2 import volumes
+from cinder.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class SchedulerHintsController(wsgi.Controller):
+
+    @staticmethod
+    def _extract_scheduler_hints(body):
+        hints = {}
+
+        attr = '%s:scheduler_hints' % Scheduler_hints.alias
+        try:
+            if attr in body:
+                hints.update(body[attr])
+        except ValueError:
+            msg = _("Malformed scheduler_hints attribute")
+            raise webob.exc.HTTPBadRequest(reason=msg)
+
+        return hints
+
+    @wsgi.extends
+    def create(self, req, body):
+        hints = self._extract_scheduler_hints(body)
+
+        if 'volume' in body:
+            body['volume']['scheduler_hints'] = hints
+        yield
+
+
+class Scheduler_hints(extensions.ExtensionDescriptor):
+    """Pass arbitrary key/value pairs to the scheduler."""
+
+    name = "SchedulerHints"
+    alias = "OS-SCH-HNT"
+    namespace = volumes.SCHEDULER_HINTS_NAMESPACE
+    updated = "2013-04-18T00:00:00+00:00"
+
+    def get_controller_extensions(self):
+        controller = SchedulerHintsController()
+        ext = extensions.ControllerExtension(self, 'volumes', controller)
+        return [ext]
index cc882c826ac0fb8a2dfd1ab71bb945c9896c6187..810b779140b2a9adc79f45d02cec6e7eee49a609 100644 (file)
@@ -179,6 +179,16 @@ class XMLDeserializer(TextDeserializer):
                                                                  listnames)
             return result
 
+    def find_first_child_named_in_namespace(self, parent, namespace, name):
+        """Search a nodes children for the first child with a given name."""
+        for node in parent.childNodes:
+            if (node.localName == name and
+                node.namespaceURI and
+                node.namespaceURI == namespace):
+                return node
+        return None
+
+
     def find_first_child_named(self, parent, name):
         """Search a nodes children for the first child with a given name"""
         for node in parent.childNodes:
index bf46449d44274feee3e0187e806a422a74a9f0fe..720b7cbe1536a01b51619cc798fee8f93ed1b876 100644 (file)
@@ -32,8 +32,8 @@ from cinder.volume import volume_types
 
 
 LOG = logging.getLogger(__name__)
-
-
+SCHEDULER_HINTS_NAMESPACE =\
+        "http://docs.openstack.org/block-service/ext/scheduler-hints/api/v2"
 FLAGS = flags.FLAGS
 
 
@@ -92,6 +92,20 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer):
 
     metadata_deserializer = common.MetadataXMLDeserializer()
 
+    def _extract_scheduler_hints(self, volume_node):
+        """Marshal the scheduler hints attribute of a parsed request."""
+        node = self.find_first_child_named_in_namespace(volume_node,
+                SCHEDULER_HINTS_NAMESPACE, "scheduler_hints")
+        if node:
+            scheduler_hints = {}
+            for child in self.extract_elements(node):
+                scheduler_hints.setdefault(child.nodeName, [])
+                value = self.extract_text(child).strip()
+                scheduler_hints[child.nodeName].append(value)
+            return scheduler_hints
+        else:
+            return None
+
     def _extract_volume(self, node):
         """Marshal the volume attribute of a parsed request."""
         volume = {}
@@ -107,6 +121,10 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer):
         if metadata_node is not None:
             volume['metadata'] = self.extract_metadata(metadata_node)
 
+        scheduler_hints = self._extract_scheduler_hints(volume_node)
+        if scheduler_hints:
+            volume['scheduler_hints'] = scheduler_hints
+
         return volume
 
 
@@ -280,6 +298,7 @@ class VolumeController(wsgi.Controller):
                 kwargs['image_id'] = image_uuid
 
         kwargs['availability_zone'] = volume.get('availability_zone', None)
+        kwargs['scheduler_hints'] = volume.get('scheduler_hints', None)
 
         new_volume = self.volume_api.create(context,
                                             size,
diff --git a/cinder/tests/api/contrib/test_scheduler_hints.py b/cinder/tests/api/contrib/test_scheduler_hints.py
new file mode 100644 (file)
index 0000000..4edc9d0
--- /dev/null
@@ -0,0 +1,101 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+# 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 datetime
+
+import cinder
+from cinder.api.openstack import wsgi
+from cinder.openstack.common import jsonutils
+from cinder import test
+from cinder.tests.api import fakes
+from cinder.tests.api.v2 import stubs
+
+UUID = fakes.FAKE_UUID
+
+
+class SchedulerHintsTestCase(test.TestCase):
+
+    def setUp(self):
+        super(SchedulerHintsTestCase, self).setUp()
+        self.fake_instance = stubs.stub_volume(1, uuid=UUID)
+        self.fake_instance['created_at'] =\
+                datetime.datetime(2013, 1, 1, 1, 1, 1)
+        self.flags(
+            osapi_volume_extension=[
+                'cinder.api.contrib.select_extensions'],
+            osapi_volume_ext_list=['Scheduler_hints'])
+        self.app = fakes.wsgi_app()
+
+    def test_create_server_without_hints(self):
+
+        @wsgi.response(202)
+        def fake_create(*args, **kwargs):
+            self.assertNotIn('scheduler_hints', kwargs['body'])
+            return self.fake_instance
+
+        self.stubs.Set(cinder.api.v2.volumes.VolumeController, 'create',
+                       fake_create)
+
+        req = fakes.HTTPRequest.blank('/v2/fake/volumes')
+        req.method = 'POST'
+        req.content_type = 'application/json'
+        body = {'id': id,
+                'volume_type_id': 'cedef40a-ed67-4d10-800e-17455edce175',
+                'volume_id': '1',
+               }
+        req.body = jsonutils.dumps(body)
+        res = req.get_response(self.app)
+        self.assertEqual(202, res.status_int)
+
+    def test_create_server_with_hints(self):
+
+        @wsgi.response(202)
+        def fake_create(*args, **kwargs):
+            self.assertIn('scheduler_hints', kwargs['body'])
+            self.assertEqual(kwargs['body']['scheduler_hints'], {"a": "b"})
+            return self.fake_instance
+
+        self.stubs.Set(cinder.api.v2.volumes.VolumeController, 'create',
+                       fake_create)
+
+        req = fakes.HTTPRequest.blank('/v2/fake/volumes')
+        req.method = 'POST'
+        req.content_type = 'application/json'
+        body = {'id': id,
+                'volume_type_id': 'cedef40a-ed67-4d10-800e-17455edce175',
+                'volume_id': '1',
+                'scheduler_hints': {'a': 'b'},
+               }
+
+        req.body = jsonutils.dumps(body)
+        res = req.get_response(self.app)
+        self.assertEqual(202, res.status_int)
+
+    def test_create_server_bad_hints(self):
+        req = fakes.HTTPRequest.blank('/v2/fake/volumes')
+        req.method = 'POST'
+        req.content_type = 'application/json'
+        body = {'volume': {
+                  'id': id,
+                  'volume_type_id': 'cedef40a-ed67-4d10-800e-17455edce175',
+                  'volume_id': '1',
+                  'scheduler_hints': 'a', }
+               }
+
+        req.body = jsonutils.dumps(body)
+        res = req.get_response(self.app)
+        self.assertEqual(400, res.status_int)
index 042ae6802ab4c2ad8a590dde791ceaa634038d0d..68550b5e318dac9bd5a64d6bd2cd6fbdaf10beed 100644 (file)
@@ -87,7 +87,8 @@ class API(base.Base):
 
     def create(self, context, size, name, description, snapshot=None,
                image_id=None, volume_type=None, metadata=None,
-               availability_zone=None, source_volume=None):
+               availability_zone=None, source_volume=None,
+               scheduler_hints=None):
 
         exclusive_options = (snapshot, image_id, source_volume)
         exclusive_options_set = sum(1 for option in
@@ -223,7 +224,10 @@ class API(base.Base):
                         'image_id': image_id,
                         'source_volid': volume['source_volid']}
 
-        filter_properties = {}
+        if scheduler_hints:
+            filter_properties = {'scheduler_hints': scheduler_hints}
+        else:
+            filter_properties = {}
 
         self._cast_create_volume(context, request_spec, filter_properties)