diff --git a/cinder/api/contrib/scheduler_hints.py b/cinder/api/contrib/scheduler_hints.py index 1bae1386257..47a1190ffb2 100644 --- a/cinder/api/contrib/scheduler_hints.py +++ b/cinder/api/contrib/scheduler_hints.py @@ -12,32 +12,28 @@ # 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.i18n import _ +from cinder.api.schemas import scheduler_hints +from cinder.api import validation class SchedulerHintsController(wsgi.Controller): - @staticmethod - def _extract_scheduler_hints(body): + @validation.schema(scheduler_hints.create) + def _extract_scheduler_hints(self, req, 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(explanation=msg) + if body.get(attr) is not None: + hints.update(body.get(attr)) return hints @wsgi.extends def create(self, req, body): - hints = self._extract_scheduler_hints(body) + attr = '%s:scheduler_hints' % Scheduler_hints.alias + scheduler_hints_body = dict.fromkeys((attr,), body.get(attr)) + hints = self._extract_scheduler_hints(req, body=scheduler_hints_body) if 'volume' in body: body['volume']['scheduler_hints'] = hints diff --git a/cinder/api/schemas/scheduler_hints.py b/cinder/api/schemas/scheduler_hints.py new file mode 100644 index 00000000000..7246bb3fd08 --- /dev/null +++ b/cinder/api/schemas/scheduler_hints.py @@ -0,0 +1,79 @@ +# Copyright (C) 2018 NTT DATA +# 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. + +""" +Schema for V3 scheduler_hints API. + +""" + +from cinder.api.validation import parameter_types + +create = { + 'type': 'object', + 'properties': { + 'OS-SCH-HNT:scheduler_hints': { + 'type': ['object', 'null'], + 'properties': { + 'local_to_instance': parameter_types.optional_uuid, + 'different_host': { + # NOTE: The value of 'different_host' is the set of volume + # uuids where a new volume is scheduled on a different + # host. A user can specify one volume as string parameter + # and should specify multiple volumes as array parameter + # instead. + 'oneOf': [ + { + 'type': 'string', + 'format': 'uuid' + }, + { + 'type': 'array', + 'items': parameter_types.uuid, + 'uniqueItems': True, + } + ] + }, + 'same_host': { + # NOTE: The value of 'same_host' is the set of volume + # uuids where a new volume is scheduled on the same host. + # A user can specify one volume as string parameter and + # should specify multiple volumes as array parameter + # instead. + 'oneOf': [ + { + 'type': 'string', + 'format': 'uuid' + }, + { + 'type': 'array', + 'items': parameter_types.uuid, + 'uniqueItems': True, + } + ] + }, + 'query': { + # NOTE: The value of 'query' is converted to dict data with + # jsonutils.loads() and used for filtering hosts. + 'type': ['string', 'object'], + }, + }, + # NOTE: As this Mail: + # http://lists.openstack.org/pipermail/openstack-dev/2015-June/067996.html + # pointed out the limit the scheduler-hints in the API is + # problematic. So relax it. + 'additionalProperties': True + }, + }, +} diff --git a/cinder/tests/unit/api/contrib/test_scheduler_hints.py b/cinder/tests/unit/api/contrib/test_scheduler_hints.py index e74929046b3..d16721bcc19 100644 --- a/cinder/tests/unit/api/contrib/test_scheduler_hints.py +++ b/cinder/tests/unit/api/contrib/test_scheduler_hints.py @@ -15,6 +15,7 @@ import datetime +import ddt from oslo_serialization import jsonutils from six.moves import http_client @@ -30,6 +31,7 @@ from cinder.tests.unit import fake_constants as fake UUID = fakes.FAKE_UUID +@ddt.ddt class SchedulerHintsTestCase(test.TestCase): def setUp(self): @@ -104,3 +106,44 @@ class SchedulerHintsTestCase(test.TestCase): req.body = jsonutils.dump_as_bytes(body) res = req.get_response(self.app) self.assertEqual(http_client.BAD_REQUEST, res.status_int) + + @ddt.data({'local_to_instance': UUID}, + {'local_to_instance': None}, + {'different_host': [fake.UUID1, fake.UUID2]}, + {'same_host': UUID}, + {'same_host': [fake.UUID1, fake.UUID2]}, + {'fake_key': 'fake_value'}, + {'query': 'query_testing'}, + {'query': {}}, + None) + def test_scheduler_hints_with_valid_body(self, value): + req = fakes.HTTPRequest.blank('/v2/%s/volumes' % fake.PROJECT_ID) + req.method = 'POST' + req.content_type = 'application/json' + body = {'volume': {'size': 1}, + 'OS-SCH-HNT:scheduler_hints': value} + + req.body = jsonutils.dump_as_bytes(body) + res = req.get_response(self.app) + self.assertEqual(http_client.ACCEPTED, res.status_int) + + @ddt.data({'local_to_instance': 'local_to_instance'}, + {'different_host': 'different_host'}, + {'different_host': ['different_host']}, + {'different_host': [UUID, UUID]}, + {'same_host': 'same_host'}, + {'same_host': ['same_host']}, + {'same_host': [UUID, UUID]}, + {'query': None}, + {'scheduler_hints'}) + def test_scheduler_hints_with_invalid_body(self, value): + req = fakes.HTTPRequest.blank('/v2/%s/volumes' % fake.PROJECT_ID) + req.method = 'POST' + req.content_type = 'application/json' + body = {'volume': {'size': 1}, + 'OS-SCH-HNT:scheduler_hints': value} + + req.body = jsonutils.dump_as_bytes(body) + res = req.get_response(self.app) + + self.assertEqual(http_client.BAD_REQUEST, res.status_int)