diff --git a/nova/api/openstack/compute/schemas/simple_tenant_usage.py b/nova/api/openstack/compute/schemas/simple_tenant_usage.py new file mode 100644 index 000000000000..c7e2a657233e --- /dev/null +++ b/nova/api/openstack/compute/schemas/simple_tenant_usage.py @@ -0,0 +1,52 @@ +# Copyright 2017 NEC Corporation. +# +# 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 copy + +from nova.api.validation import parameter_types + + +index_query = { + 'type': 'object', + 'properties': { + 'start': parameter_types.multi_params({'type': 'string'}), + 'end': parameter_types.multi_params({'type': 'string'}), + 'detailed': parameter_types.multi_params({'type': 'string'}) + }, + # NOTE(gmann): This is kept True to keep backward compatibility. + # As of now Schema validation stripped out the additional parameters and + # does not raise 400. In the future, we may block the additional parameters + # by bump in Microversion. + 'additionalProperties': True +} + +show_query = { + 'type': 'object', + 'properties': { + 'start': parameter_types.multi_params({'type': 'string'}), + 'end': parameter_types.multi_params({'type': 'string'}) + }, + # NOTE(gmann): This is kept True to keep backward compatibility. + # As of now Schema validation stripped out the additional parameters and + # does not raise 400. In the future, we may block the additional parameters + # by bump in Microversion. + 'additionalProperties': True +} + +index_query_v240 = copy.deepcopy(index_query) +index_query_v240['properties'].update( + parameter_types.pagination_parameters) + +show_query_v240 = copy.deepcopy(show_query) +show_query_v240['properties'].update( + parameter_types.pagination_parameters) diff --git a/nova/api/openstack/compute/simple_tenant_usage.py b/nova/api/openstack/compute/simple_tenant_usage.py index c0b3512c1d96..a879dbd0d807 100644 --- a/nova/api/openstack/compute/simple_tenant_usage.py +++ b/nova/api/openstack/compute/simple_tenant_usage.py @@ -22,9 +22,11 @@ import six.moves.urllib.parse as urlparse from webob import exc from nova.api.openstack import common +from nova.api.openstack.compute.schemas import simple_tenant_usage as schema from nova.api.openstack.compute.views import usages as usages_view from nova.api.openstack import extensions from nova.api.openstack import wsgi +from nova.api import validation import nova.conf from nova import context as nova_context from nova import exception @@ -261,24 +263,28 @@ class SimpleTenantUsageController(wsgi.Controller): return (period_start, period_stop, detailed) @wsgi.Controller.api_version("2.40") + @validation.query_schema(schema.index_query_v240) @extensions.expected_errors(400) def index(self, req): """Retrieve tenant_usage for all tenants.""" return self._index(req, links=True) @wsgi.Controller.api_version("2.1", "2.39") # noqa + @validation.query_schema(schema.index_query) @extensions.expected_errors(400) def index(self, req): """Retrieve tenant_usage for all tenants.""" return self._index(req) @wsgi.Controller.api_version("2.40") + @validation.query_schema(schema.show_query_v240) @extensions.expected_errors(400) def show(self, req, id): """Retrieve tenant_usage for a specified tenant.""" return self._show(req, id, links=True) @wsgi.Controller.api_version("2.1", "2.39") # noqa + @validation.query_schema(schema.show_query) @extensions.expected_errors(400) def show(self, req, id): """Retrieve tenant_usage for a specified tenant.""" diff --git a/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py b/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py index b32a881314e3..9e6c2a078279 100644 --- a/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py +++ b/nova/tests/unit/api/openstack/compute/test_simple_tenant_usage.py @@ -367,6 +367,53 @@ class SimpleTenantUsageTestV21(test.TestCase): self._test_get_tenants_usage_with_one_date( 'start=%s' % (NOW - datetime.timedelta(5)).isoformat()) + def test_index_additional_query_parameters(self): + req = fakes.HTTPRequest.blank('?start=%s&end=%s&additional=1' % + (START.isoformat(), STOP.isoformat()), + version=self.version) + res = self.controller.index(req) + self.assertIn('tenant_usages', res) + + def _test_index_duplicate_query_parameters_validation(self, params): + for param, value in params.items(): + req = fakes.HTTPRequest.blank('?start=%s&%s=%s&%s=%s' % + (START.isoformat(), param, value, param, value), + version=self.version) + + res = self.controller.index(req) + self.assertIn('tenant_usages', res) + + def test_index_duplicate_query_parameters_validation(self): + params = { + 'start': START.isoformat(), + 'end': STOP.isoformat(), + 'detailed': 1 + } + self._test_index_duplicate_query_parameters_validation(params) + + def test_show_additional_query_parameters(self): + req = fakes.HTTPRequest.blank('?start=%s&end=%s&additional=1' % + (START.isoformat(), STOP.isoformat()), + version=self.version) + res = self.controller.show(req, 1) + self.assertIn('tenant_usage', res) + + def _test_show_duplicate_query_parameters_validation(self, params): + for param, value in params.items(): + req = fakes.HTTPRequest.blank('?start=%s&%s=%s&%s=%s' % + (START.isoformat(), param, value, param, value), + version=self.version) + + res = self.controller.show(req, 1) + self.assertIn('tenant_usage', res) + + def test_show_duplicate_query_parameters_validation(self): + params = { + 'start': START.isoformat(), + 'end': STOP.isoformat() + } + self._test_show_duplicate_query_parameters_validation(params) + class SimpleTenantUsageTestV40(SimpleTenantUsageTestV21): version = '2.40' @@ -383,6 +430,29 @@ class SimpleTenantUsageTestV40(SimpleTenantUsageTestV21): self._test_verify_index(START, STOP, limit=SERVERS * TENANTS) + @mock.patch('nova.objects.InstanceList.get_active_by_window_joined', + fake_get_active_by_window_joined) + def test_index_duplicate_query_parameters_validation(self): + params = { + 'start': START.isoformat(), + 'end': STOP.isoformat(), + 'detailed': 1, + 'limit': 1, + 'marker': 1 + } + self._test_index_duplicate_query_parameters_validation(params) + + @mock.patch('nova.objects.InstanceList.get_active_by_window_joined', + fake_get_active_by_window_joined) + def test_show_duplicate_query_parameters_validation(self): + params = { + 'start': START.isoformat(), + 'end': STOP.isoformat(), + 'limit': 1, + 'marker': 1 + } + self._test_show_duplicate_query_parameters_validation(params) + class SimpleTenantUsageLimitsTestV21(test.TestCase): version = '2.1' @@ -449,6 +519,36 @@ class SimpleTenantUsageLimitsTestV240(SimpleTenantUsageLimitsTestV21): self.assertRaises( webob.exc.HTTPBadRequest, self.controller.index, req) + def test_index_with_invalid_non_int_limit(self): + req = self._get_request('?start=%s&end=%s&limit=-3') + self.assertRaises(exception.ValidationError, + self.controller.index, req) + + def test_index_with_invalid_string_limit(self): + req = self._get_request('?start=%s&end=%s&limit=abc') + self.assertRaises(exception.ValidationError, + self.controller.index, req) + + def test_index_duplicate_query_with_invalid_string_limit(self): + req = self._get_request('?start=%s&end=%s&limit=3&limit=abc') + self.assertRaises(exception.ValidationError, + self.controller.index, req) + + def test_show_with_invalid_non_int_limit(self): + req = self._get_request('?start=%s&end=%s&limit=-3') + self.assertRaises(exception.ValidationError, + self.controller.show, req) + + def test_show_with_invalid_string_limit(self): + req = self._get_request('?start=%s&end=%s&limit=abc') + self.assertRaises(exception.ValidationError, + self.controller.show, req) + + def test_show_duplicate_query_with_invalid_string_limit(self): + req = self._get_request('?start=%s&end=%s&limit=3&limit=abc') + self.assertRaises(exception.ValidationError, + self.controller.show, req) + class SimpleTenantUsageControllerTestV21(test.TestCase): controller = simple_tenant_usage_v21.SimpleTenantUsageController()