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:
parent
1d6de0f42c
commit
463aa30b3d
|
@ -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]
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue