Implement scheduler hints for API v2

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
This commit is contained in:
Nikolaj Starodubtsev 2013-04-18 16:42:35 +04:00
parent 1d6de0f42c
commit 463aa30b3d
5 changed files with 201 additions and 4 deletions

View File

@ -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]

View 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:

View 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,

View File

@ -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)

View 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)