Merge "api: Restrict additional query string arguments"

This commit is contained in:
Zuul
2026-02-28 19:22:07 +00:00
committed by Gerrit Code Review
58 changed files with 386 additions and 164 deletions
+2 -1
View File
@@ -283,7 +283,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
202 Accepted instead of HTTP 200 and a volumeAttachment response. 202 Accepted instead of HTTP 200 and a volumeAttachment response.
* 2.102 - Add support for filtering flavors by name. Remove the deprecated * 2.102 - Add support for filtering flavors by name. Remove the deprecated
``rxtx_factor`` and ``OS-FLV-DISABLED:disabled`` fields and ``rxtx_factor`` and ``OS-FLV-DISABLED:disabled`` fields and
filters from various flavors APIs. filters from various flavors APIs and restrict additional query
string parameters for all APIs.
""" """
# The minimum and maximum versions of the API supported # The minimum and maximum versions of the API supported
+4 -2
View File
@@ -50,7 +50,8 @@ class AggregateController(wsgi.Controller):
self.conductor_tasks = conductor.ComputeTaskAPI() self.conductor_tasks = conductor.ComputeTaskAPI()
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response, '2.1', '2.40') @validation.response_body_schema(schema.index_response, '2.1', '2.40')
@validation.response_body_schema(schema.index_response_v241, '2.41') @validation.response_body_schema(schema.index_response_v241, '2.41')
def index(self, req): def index(self, req):
@@ -100,7 +101,8 @@ class AggregateController(wsgi.Controller):
return agg return agg
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, '2.1', '2.40') @validation.response_body_schema(schema.show_response, '2.1', '2.40')
@validation.response_body_schema(schema.show_response_v241, '2.41') @validation.response_body_schema(schema.show_response_v241, '2.41')
def show(self, req, id): def show(self, req, id):
@@ -65,7 +65,7 @@ class AssistedVolumeSnapshotsController(wsgi.Controller):
@wsgi.response(204) @wsgi.response(204)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.query_schema(schema.delete_query, '2.0', '2.74') @validation.query_schema(schema.delete_query, '2.0', '2.74')
@validation.query_schema(schema.delete_query_275, '2.75') @validation.query_schema(schema.delete_query_v275, '2.75')
@validation.response_body_schema(schema.delete_response) @validation.response_body_schema(schema.delete_response)
def delete(self, req, id): def delete(self, req, id):
"""Delete a snapshot.""" """Delete a snapshot."""
@@ -65,7 +65,8 @@ class InterfaceAttachmentController(wsgi.Controller):
self.network_api = neutron.API() self.network_api = neutron.API()
@wsgi.expected_errors((404, 501)) @wsgi.expected_errors((404, 501))
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response, '2.1', '2.69') @validation.response_body_schema(schema.index_response, '2.1', '2.69')
@validation.response_body_schema(schema.index_response_v270, '2.70') @validation.response_body_schema(schema.index_response_v270, '2.70')
def index(self, req, server_id): def index(self, req, server_id):
@@ -110,7 +111,8 @@ class InterfaceAttachmentController(wsgi.Controller):
return {'interfaceAttachments': results} return {'interfaceAttachments': results}
@wsgi.expected_errors((403, 404)) @wsgi.expected_errors((403, 404))
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, '2.1', '2.69') @validation.response_body_schema(schema.show_response, '2.1', '2.69')
@validation.response_body_schema(schema.show_response_v270, '2.70') @validation.response_body_schema(schema.show_response_v270, '2.70')
def show(self, req, server_id, id): def show(self, req, server_id, id):
@@ -106,7 +106,8 @@ class AvailabilityZoneController(wsgi.Controller):
return {'availabilityZoneInfo': result} return {'availabilityZoneInfo': result}
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req): def index(self, req):
"""Returns a summary list of availability zone.""" """Returns a summary list of availability zone."""
@@ -116,7 +117,8 @@ class AvailabilityZoneController(wsgi.Controller):
return self._describe_availability_zones(context) return self._describe_availability_zones(context)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.detail_query) @validation.query_schema(schema.detail_query, '2.1', '2.101')
@validation.query_schema(schema.detail_query_v2102, '2.102')
@validation.response_body_schema(schema.detail_response) @validation.response_body_schema(schema.detail_response)
def detail(self, req): def detail(self, req):
"""Returns a detailed list of availability zone.""" """Returns a detailed list of availability zone."""
+4 -2
View File
@@ -856,7 +856,8 @@ EXTENSION_LIST_LEGACY_V2_COMPATIBLE = sorted(
class ExtensionInfoController(wsgi.Controller): class ExtensionInfoController(wsgi.Controller):
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req): def index(self, req):
context = req.environ['nova.context'] context = req.environ['nova.context']
@@ -870,7 +871,8 @@ class ExtensionInfoController(wsgi.Controller):
return dict(extensions=EXTENSION_LIST) return dict(extensions=EXTENSION_LIST)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response) @validation.response_body_schema(schema.show_response)
def show(self, req, id): def show(self, req, id):
context = req.environ['nova.context'] context = req.environ['nova.context']
+2 -1
View File
@@ -42,7 +42,8 @@ class FlavorAccessController(wsgi.Controller):
"""The flavor access API controller for the OpenStack API.""" """The flavor access API controller for the OpenStack API."""
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req, flavor_id): def index(self, req, flavor_id):
context = req.environ['nova.context'] context = req.environ['nova.context']
+2 -1
View File
@@ -171,7 +171,8 @@ class FlavorsController(wsgi.Controller):
req, limited_flavors, include_extra_specs=include_extra_specs) req, limited_flavors, include_extra_specs=include_extra_specs)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.0', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, '2.0', '2.54') @validation.response_body_schema(schema.show_response, '2.0', '2.54')
@validation.response_body_schema(schema.show_response_v255, '2.55', '2.60') @validation.response_body_schema(schema.show_response_v255, '2.55', '2.60')
@validation.response_body_schema(schema.show_response_v261, '2.61', '2.74') @validation.response_body_schema(schema.show_response_v261, '2.61', '2.74')
@@ -55,7 +55,8 @@ class FlavorExtraSpecsController(wsgi.Controller):
validators.validate(name, value) validators.validate(name, value)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req, flavor_id): def index(self, req, flavor_id):
"""Returns the list of extra specs for a given flavor.""" """Returns the list of extra specs for a given flavor."""
@@ -108,7 +109,8 @@ class FlavorExtraSpecsController(wsgi.Controller):
return body return body
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response) @validation.response_body_schema(schema.show_response)
def show(self, req, flavor_id, id): def show(self, req, flavor_id, id):
"""Return a single extra spec item.""" """Return a single extra spec item."""
@@ -139,7 +139,8 @@ class InstanceActionsController(wsgi.Controller):
return actions_dict return actions_dict
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, "2.1", "2.50") @validation.response_body_schema(schema.show_response, "2.1", "2.50")
@validation.response_body_schema(schema.show_response_v251, "2.51", "2.57") @validation.response_body_schema(schema.show_response_v251, "2.51", "2.57")
@validation.response_body_schema(schema.show_response_v258, "2.58", "2.61") @validation.response_body_schema(schema.show_response_v258, "2.58", "2.61")
@@ -35,7 +35,8 @@ class InstanceUsageAuditLogController(wsgi.Controller):
self.host_api = compute.HostAPI() self.host_api = compute.HostAPI()
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req): def index(self, req):
context = req.environ['nova.context'] context = req.environ['nova.context']
@@ -44,7 +45,8 @@ class InstanceUsageAuditLogController(wsgi.Controller):
return {'instance_usage_audit_logs': task_log} return {'instance_usage_audit_logs': task_log}
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response) @validation.response_body_schema(schema.show_response)
def show(self, req, id): def show(self, req, id):
context = req.environ['nova.context'] context = req.environ['nova.context']
+4 -2
View File
@@ -35,7 +35,8 @@ class IPsController(wsgi.Controller):
self._compute_api = compute.API() self._compute_api = compute.API()
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req, server_id): def index(self, req, server_id):
context = req.environ["nova.context"] context = req.environ["nova.context"]
@@ -46,7 +47,8 @@ class IPsController(wsgi.Controller):
return self._view_builder.index(req, networks) return self._view_builder.index(req, networks)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response) @validation.response_body_schema(schema.show_response)
def show(self, req, server_id, id): def show(self, req, server_id, id):
context = req.environ["nova.context"] context = req.environ["nova.context"]
+10 -10
View File
@@ -118,9 +118,9 @@ class KeypairController(wsgi.Controller):
@wsgi.response(202, '2.0', '2.1') @wsgi.response(202, '2.0', '2.1')
@wsgi.response(204, '2.2') @wsgi.response(204, '2.2')
@validation.query_schema(schema.delete_query_schema_v20, '2.0', '2.9') @validation.query_schema(schema.delete_query_v20, '2.0', '2.9')
@validation.query_schema(schema.delete_query_schema_v210, '2.10', '2.74') @validation.query_schema(schema.delete_query_v210, '2.10', '2.74')
@validation.query_schema(schema.delete_query_schema_v275, '2.75') @validation.query_schema(schema.delete_query_v275, '2.75')
@validation.response_body_schema(schema.delete_response) @validation.response_body_schema(schema.delete_response)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
def delete(self, req, id): def delete(self, req, id):
@@ -143,9 +143,9 @@ class KeypairController(wsgi.Controller):
except exception.KeypairNotFound as exc: except exception.KeypairNotFound as exc:
raise webob.exc.HTTPNotFound(explanation=exc.format_message()) raise webob.exc.HTTPNotFound(explanation=exc.format_message())
@validation.query_schema(schema.show_query_schema_v20, '2.0', '2.9') @validation.query_schema(schema.show_query_v20, '2.0', '2.9')
@validation.query_schema(schema.show_query_schema_v210, '2.10', '2.74') @validation.query_schema(schema.show_query_v210, '2.10', '2.74')
@validation.query_schema(schema.show_query_schema_v275, '2.75') @validation.query_schema(schema.show_query_v275, '2.75')
@validation.response_body_schema(schema.show_response, '2.0', '2.1') @validation.response_body_schema(schema.show_response, '2.0', '2.1')
@validation.response_body_schema(schema.show_response_v22, '2.2') @validation.response_body_schema(schema.show_response_v22, '2.2')
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@@ -174,10 +174,10 @@ class KeypairController(wsgi.Controller):
raise webob.exc.HTTPNotFound(explanation=exc.format_message()) raise webob.exc.HTTPNotFound(explanation=exc.format_message())
return self._view_builder.show(keypair, key_type=key_type) return self._view_builder.show(keypair, key_type=key_type)
@validation.query_schema(schema.index_query_schema_v20, '2.0', '2.9') @validation.query_schema(schema.index_query_v20, '2.0', '2.9')
@validation.query_schema(schema.index_query_schema_v210, '2.10', '2.34') @validation.query_schema(schema.index_query_v210, '2.10', '2.34')
@validation.query_schema(schema.index_query_schema_v235, '2.35', '2.74') @validation.query_schema(schema.index_query_v235, '2.35', '2.74')
@validation.query_schema(schema.index_query_schema_v275, '2.75') @validation.query_schema(schema.index_query_v275, '2.75')
@validation.response_body_schema(schema.index_response, '2.0', '2.1') @validation.response_body_schema(schema.index_response, '2.0', '2.1')
@validation.response_body_schema(schema.index_response_v22, '2.2', '2.34') @validation.response_body_schema(schema.index_response_v22, '2.2', '2.34')
@validation.response_body_schema(schema.index_response_v235, '2.35') @validation.response_body_schema(schema.index_response_v235, '2.35')
+2 -1
View File
@@ -87,7 +87,8 @@ class QuotaClassSetsController(wsgi.Controller):
return [] return []
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, '2.1', '2.49') @validation.response_body_schema(schema.show_response, '2.1', '2.49')
@validation.response_body_schema(schema.show_response_v250, '2.50', '2.56') # noqa: E501 @validation.response_body_schema(schema.show_response_v250, '2.50', '2.56') # noqa: E501
@validation.response_body_schema(schema.show_response_v257, '2.57') @validation.response_body_schema(schema.show_response_v257, '2.57')
@@ -125,19 +125,21 @@ set_metadata = {
'additionalProperties': False, 'additionalProperties': False,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_aggregate_response = { _aggregate_response = {
'type': 'object', 'type': 'object',
@@ -65,8 +65,8 @@ delete_query = {
'additionalProperties': True 'additionalProperties': True
} }
delete_query_275 = copy.deepcopy(delete_query) delete_query_v275 = copy.deepcopy(delete_query)
delete_query_275['additionalProperties'] = False delete_query_v275['additionalProperties'] = False
create_response = { create_response = {
'type': 'object', 'type': 'object',
@@ -16,7 +16,6 @@ import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
create = { create = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -50,20 +49,24 @@ create = {
create_v249 = copy.deepcopy(create) create_v249 = copy.deepcopy(create)
create_v249['properties']['interfaceAttachment']['properties']['tag'] = parameter_types.tag # noqa: E501 create_v249['properties']['interfaceAttachment']['properties']['tag'] = parameter_types.tag # noqa: E501
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_interface_attachment = { _interface_attachment = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -14,15 +14,23 @@
import copy import copy
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
detail_query = index_query index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
detail_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
detail_query_v2102 = copy.deepcopy(detail_query)
detail_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
@@ -10,19 +10,23 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO(stephenfin): Remove additionalProperties in a future API version import copy
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_extension_obj = { _extension_obj = {
'type': 'object', 'type': 'object',
@@ -16,7 +16,6 @@ import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
add_tenant_access = { add_tenant_access = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -55,13 +54,15 @@ remove_tenant_access = {
'additionalProperties': False, 'additionalProperties': False,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
_common_response = { _common_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -144,13 +144,15 @@ index_query_v2102['properties']['name'] = parameter_types.multi_params(
index_query_v2102['properties']['sort_key']['items']['enum'].remove( index_query_v2102['properties']['sort_key']['items']['enum'].remove(
'rxtx_factor') 'rxtx_factor')
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_flavor_basic = { _flavor_basic = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -39,20 +39,24 @@ update.update({
'maxProperties': 1 'maxProperties': 1
}) })
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -10,13 +10,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO(stephenfin): Remove additionalProperties in a future API version import copy
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -49,6 +49,9 @@ show_query = {
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -10,18 +10,26 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_instance_usage_audit_log_response = { _instance_usage_audit_log_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
+6 -2
View File
@@ -12,20 +12,24 @@
import copy import copy
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_ip_address = { _ip_address = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
+14 -16
View File
@@ -89,35 +89,33 @@ create_v292['properties']['keypair']['properties']['name'] = (
parameter_types.keypair_name_special_chars_v292) parameter_types.keypair_name_special_chars_v292)
create_v292['properties']['keypair']['required'] = ['name', 'public_key'] create_v292['properties']['keypair']['required'] = ['name', 'public_key']
index_query_schema_v20 = { index_query_v20 = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True 'additionalProperties': True
} }
index_query_v210 = {
index_query_schema_v210 = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'user_id': parameter_types.multi_params({'type': 'string'}) 'user_id': parameter_types.multi_params({'type': 'string'})
}, },
'additionalProperties': True 'additionalProperties': True
} }
index_query_v235 = copy.deepcopy(index_query_v210)
index_query_schema_v235 = copy.deepcopy(index_query_schema_v210) index_query_v235['properties'].update(
index_query_schema_v235['properties'].update(
parameter_types.pagination_parameters) parameter_types.pagination_parameters)
index_query_v275 = copy.deepcopy(index_query_v235)
index_query_v275['additionalProperties'] = False
show_query_schema_v20 = index_query_schema_v20 show_query_v20 = index_query_v20
show_query_schema_v210 = index_query_schema_v210 show_query_v210 = index_query_v210
delete_query_schema_v20 = index_query_schema_v20 show_query_v275 = copy.deepcopy(show_query_v210)
delete_query_schema_v210 = index_query_schema_v210 show_query_v275['additionalProperties'] = False
index_query_schema_v275 = copy.deepcopy(index_query_schema_v235) delete_query_v20 = index_query_v20
index_query_schema_v275['additionalProperties'] = False delete_query_v210 = index_query_v210
show_query_schema_v275 = copy.deepcopy(show_query_schema_v210) delete_query_v275 = copy.deepcopy(delete_query_v210)
show_query_schema_v275['additionalProperties'] = False delete_query_v275['additionalProperties'] = False
delete_query_schema_v275 = copy.deepcopy(delete_query_schema_v210)
delete_query_schema_v275['additionalProperties'] = False
create_response = { create_response = {
'type': 'object', 'type': 'object',
@@ -17,7 +17,6 @@ import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
from nova.api.validation import response_types from nova.api.validation import response_types
index_query_v20 = { index_query_v20 = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy import copy
from nova.api.openstack.compute.schemas import quota_sets from nova.api.openstack.compute.schemas import quota_sets
@@ -45,13 +46,15 @@ del update_v257['properties']['quota_class_set']['properties'][
del update_v257['properties']['quota_class_set']['properties'][ del update_v257['properties']['quota_class_set']['properties'][
'injected_file_path_bytes'] 'injected_file_path_bytes']
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_quota_response = { _quota_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -101,15 +101,17 @@ detail_query_v275 = copy.deepcopy(show_query_v275)
update_query = copy.deepcopy(show_query) update_query = copy.deepcopy(show_query)
update_query_v275 = copy.deepcopy(show_query_v275) update_query_v275 = copy.deepcopy(show_query_v275)
# TODO(stephenfin): Remove additionalProperties in a future API version delete_query = copy.deepcopy(show_query)
delete_query_v275 = copy.deepcopy(show_query_v275)
defaults_query = { defaults_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
delete_query = copy.deepcopy(show_query) defaults_query_v2102 = copy.deepcopy(defaults_query)
delete_query_v275 = copy.deepcopy(show_query_v275) defaults_query_v2102['additionalProperties'] = False
_quota_response = { _quota_response = {
'type': 'object', 'type': 'object',
@@ -94,13 +94,15 @@ index_query = {
'additionalProperties': True 'additionalProperties': True
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
index_server_query = { index_server_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_server_query_v2102 = copy.deepcopy(index_server_query)
index_server_query_v2102['additionalProperties'] = False
# TODO(stephenfin): Remove additionalProperties in a future API version # TODO(stephenfin): Remove additionalProperties in a future API version
add_security_group = { add_security_group = {
'type': 'object', 'type': 'object',
@@ -10,12 +10,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO(stephenfin): Remove additionalProperties in a future API version import copy
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
# NOTE(stephenfin): We could define all available response types for the # NOTE(stephenfin): We could define all available response types for the
# various virt drivers, but we'd need to be able to do this (accurately) for # various virt drivers, but we'd need to be able to do this (accurately) for
@@ -99,6 +99,9 @@ show_query = {
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_server_group_response = { _server_group_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -51,20 +51,23 @@ update_all = {
'additionalProperties': False, 'additionalProperties': False,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -17,6 +17,23 @@ import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
index_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
show_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
force_complete = { force_complete = {
'type': 'object', 'type': 'object',
@@ -29,20 +46,6 @@ force_complete = {
'additionalProperties': False, 'additionalProperties': False,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
force_complete_response = { force_complete_response = {
'type': 'null', 'type': 'null',
} }
@@ -10,13 +10,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO(stephenfin): Remove additionalProperties in a future API version import copy
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
from nova.objects import instance from nova.objects import instance
@@ -32,20 +34,24 @@ update = {
"type": "null", "type": "null",
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
show_response = {'type': 'null'} show_response = {'type': 'null'}
index_response = { index_response = {
@@ -10,12 +10,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO(stephenfin): Remove additionalProperties in a future API version import copy
query_params_v21 = {
index_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
index_query_v2102 = copy.deepcopy(index_query)
index_query_v2102['additionalProperties'] = False
index_response = { index_response = {
'type': 'object', 'type': 'object',
+15 -12
View File
@@ -619,7 +619,7 @@ VALID_SORT_KEYS_V275['enum'] = list(
set(VALID_SORT_KEYS_V273["enum"]) - set(SERVER_LIST_IGNORE_SORT_KEY_V273) set(VALID_SORT_KEYS_V273["enum"]) - set(SERVER_LIST_IGNORE_SORT_KEY_V273)
) )
query_params_v21 = { index_query = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'user_id': parameter_types.common_query_param, 'user_id': parameter_types.common_query_param,
@@ -692,38 +692,38 @@ query_params_v21 = {
# Update the joined-table fields to the list so it will not be # Update the joined-table fields to the list so it will not be
# stripped in later process, thus can be handled later in api # stripped in later process, thus can be handled later in api
# to raise HTTP 400. # to raise HTTP 400.
query_params_v21['properties'].update( index_query['properties'].update(
JOINED_TABLE_QUERY_PARAMS_SERVERS) JOINED_TABLE_QUERY_PARAMS_SERVERS)
query_params_v21['properties'].update( index_query['properties'].update(
parameter_types.pagination_parameters) parameter_types.pagination_parameters)
query_params_v226 = copy.deepcopy(query_params_v21) index_query_v226 = copy.deepcopy(index_query)
query_params_v226['properties'].update({ index_query_v226['properties'].update({
'tags': parameter_types.common_query_regex_param, 'tags': parameter_types.common_query_regex_param,
'tags-any': parameter_types.common_query_regex_param, 'tags-any': parameter_types.common_query_regex_param,
'not-tags': parameter_types.common_query_regex_param, 'not-tags': parameter_types.common_query_regex_param,
'not-tags-any': parameter_types.common_query_regex_param, 'not-tags-any': parameter_types.common_query_regex_param,
}) })
query_params_v266 = copy.deepcopy(query_params_v226) index_query_v266 = copy.deepcopy(index_query_v226)
query_params_v266['properties'].update({ index_query_v266['properties'].update({
'changes-before': parameter_types.multi_params( 'changes-before': parameter_types.multi_params(
{'type': 'string', 'format': 'date-time'} {'type': 'string', 'format': 'date-time'}
), ),
}) })
query_params_v273 = copy.deepcopy(query_params_v266) index_query_v273 = copy.deepcopy(index_query_v266)
query_params_v273['properties'].update({ index_query_v273['properties'].update({
'sort_key': parameter_types.multi_params(VALID_SORT_KEYS_V273), 'sort_key': parameter_types.multi_params(VALID_SORT_KEYS_V273),
'locked': parameter_types.common_query_param, 'locked': parameter_types.common_query_param,
}) })
query_params_v275 = copy.deepcopy(query_params_v273) index_query_v275 = copy.deepcopy(index_query_v273)
query_params_v275['properties'].update({ index_query_v275['properties'].update({
'sort_key': parameter_types.multi_params(VALID_SORT_KEYS_V275), 'sort_key': parameter_types.multi_params(VALID_SORT_KEYS_V275),
}) })
query_params_v275['additionalProperties'] = False index_query_v275['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
@@ -731,6 +731,9 @@ show_query = {
'additionalProperties': True, 'additionalProperties': True,
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_server_status = { _server_status = {
'type': 'string', 'type': 'string',
'enum': [ 'enum': [
@@ -68,7 +68,6 @@ update_v253 = {
'additionalProperties': False 'additionalProperties': False
} }
index_query = { index_query = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
create = { create = {
@@ -52,6 +54,9 @@ show_query = {
'additionalProperties': True 'additionalProperties': True
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_snapshot_response = { _snapshot_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -10,14 +10,15 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# TODO(stephenfin): Remove additionalProperties in a future API version # NOTE(stephenfin): We would like to change additionalProperties to false, but
# these APIs are unversioned so we can't
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True, 'additionalProperties': True,
} }
# TODO(stephenfin): Remove additionalProperties in a future API version
multi_query = { multi_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
@@ -14,6 +14,33 @@ import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
index_query = {
'type': 'object',
'properties': {
'limit': parameter_types.multi_params(
parameter_types.non_negative_integer),
'offset': parameter_types.multi_params(
parameter_types.non_negative_integer)
},
# 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 microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
index_query_v275 = copy.deepcopy(index_query)
index_query_v275['additionalProperties'] = False
show_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
create = { create = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -76,31 +103,6 @@ update_v285 = {
'additionalProperties': False, 'additionalProperties': False,
} }
index_query = {
'type': 'object',
'properties': {
'limit': parameter_types.multi_params(
parameter_types.non_negative_integer),
'offset': parameter_types.multi_params(
parameter_types.non_negative_integer)
},
# 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 microversion 2.75, we have blocked the additional
# parameters.
'additionalProperties': True
}
index_query_v275 = copy.deepcopy(index_query)
index_query_v275['additionalProperties'] = False
# TODO(stephenfin): Remove additionalProperties in a future API version
show_query = {
'type': 'object',
'properties': {},
'additionalProperties': True,
}
_volume_attachment_response = { _volume_attachment_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
from nova.api.validation import parameter_types from nova.api.validation import parameter_types
from nova.api.validation import response_types from nova.api.validation import response_types
@@ -58,14 +60,23 @@ index_query = {
'additionalProperties': True 'additionalProperties': True
} }
index_query_v275 = copy.deepcopy(index_query)
index_query_v275['additionalProperties'] = False
detail_query = index_query detail_query = index_query
detail_query_v2102 = copy.deepcopy(detail_query)
detail_query_v2102['additionalProperties'] = False
show_query = { show_query = {
'type': 'object', 'type': 'object',
'properties': {}, 'properties': {},
'additionalProperties': True 'additionalProperties': True
} }
show_query_v2102 = copy.deepcopy(show_query)
show_query_v2102['additionalProperties'] = False
_volume_response = { _volume_response = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -366,7 +366,8 @@ class ServerSecurityGroupController(
): ):
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_server_query) @validation.query_schema(schema.index_server_query, '2.1', '2.101')
@validation.query_schema(schema.index_server_query_v2102, '2.102')
@validation.response_body_schema(schema.index_server_response) @validation.response_body_schema(schema.index_server_response)
def index(self, req, server_id): def index(self, req, server_id):
"""Returns a list of security groups for the given instance.""" """Returns a list of security groups for the given instance."""
@@ -35,7 +35,8 @@ class ServerDiagnosticsController(wsgi.Controller):
self.compute_api = compute.API() self.compute_api = compute.API()
@wsgi.expected_errors((400, 404, 409, 501)) @wsgi.expected_errors((400, 404, 409, 501))
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response, '2.1', '2.47') @validation.response_body_schema(schema.index_response, '2.1', '2.47')
@validation.response_body_schema(schema.index_response_v248, '2.48') @validation.response_body_schema(schema.index_response_v248, '2.48')
def index(self, req, server_id): def index(self, req, server_id):
+2 -1
View File
@@ -131,7 +131,8 @@ class ServerGroupController(wsgi.Controller):
return server_group return server_group
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, '2.1', '2.12') @validation.response_body_schema(schema.show_response, '2.1', '2.12')
@validation.response_body_schema(schema.show_response_v213, '2.13', '2.14') @validation.response_body_schema(schema.show_response_v213, '2.13', '2.14')
@validation.response_body_schema(schema.show_response_v215, '2.15', '2.63') @validation.response_body_schema(schema.show_response_v215, '2.15', '2.63')
@@ -49,7 +49,8 @@ class ServerMetadataController(wsgi.Controller):
return meta_dict return meta_dict
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req, server_id): def index(self, req, server_id):
"""Returns the list of metadata for a given instance.""" """Returns the list of metadata for a given instance."""
@@ -126,7 +127,8 @@ class ServerMetadataController(wsgi.Controller):
state_error, 'update metadata', server.uuid) state_error, 'update metadata', server.uuid)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response) @validation.response_body_schema(schema.show_response)
def show(self, req, server_id, id): def show(self, req, server_id, id):
"""Return a single metadata item.""" """Return a single metadata item."""
@@ -119,7 +119,8 @@ class ServerMigrationsController(wsgi.Controller):
@wsgi.api_version("2.23") @wsgi.api_version("2.23")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.23', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response_v223, '2.23', '2.58') # noqa: E501 @validation.response_body_schema(schema.index_response_v223, '2.23', '2.58') # noqa: E501
@validation.response_body_schema(schema.index_response_v259, '2.59', '2.79') # noqa: E501 @validation.response_body_schema(schema.index_response_v259, '2.59', '2.79') # noqa: E501
@validation.response_body_schema(schema.index_response_v280, '2.80') @validation.response_body_schema(schema.index_response_v280, '2.80')
@@ -148,7 +149,8 @@ class ServerMigrationsController(wsgi.Controller):
@wsgi.api_version("2.23") @wsgi.api_version("2.23")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.23', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response_v223, '2.23', '2.58') @validation.response_body_schema(schema.show_response_v223, '2.23', '2.58')
@validation.response_body_schema(schema.show_response_v259, '2.59', '2.79') # noqa: E501 @validation.response_body_schema(schema.show_response_v259, '2.59', '2.79') # noqa: E501
@validation.response_body_schema(schema.show_response_v280, '2.80') @validation.response_body_schema(schema.show_response_v280, '2.80')
@@ -33,7 +33,8 @@ class ServerPasswordController(wsgi.Controller):
self.compute_api = compute.API() self.compute_api = compute.API()
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query, '2.1', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req, server_id): def index(self, req, server_id):
context = req.environ['nova.context'] context = req.environ['nova.context']
@@ -28,7 +28,8 @@ class ServerTopologyController(wsgi.Controller):
@wsgi.api_version("2.78") @wsgi.api_version("2.78")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.query_params_v21) @validation.query_schema(schema.index_query, '2.78', '2.101')
@validation.query_schema(schema.index_query_v2102, '2.102')
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
def index(self, req, server_id): def index(self, req, server_id):
context = req.environ["nova.context"] context = req.environ["nova.context"]
+12 -11
View File
@@ -114,11 +114,11 @@ class ServersController(wsgi.Controller):
self.compute_api = compute.API() self.compute_api = compute.API()
@wsgi.expected_errors((400, 403)) @wsgi.expected_errors((400, 403))
@validation.query_schema(schema.query_params_v21, '2.1', '2.25') @validation.query_schema(schema.index_query, '2.1', '2.25')
@validation.query_schema(schema.query_params_v226, '2.26', '2.65') @validation.query_schema(schema.index_query_v226, '2.26', '2.65')
@validation.query_schema(schema.query_params_v266, '2.66', '2.72') @validation.query_schema(schema.index_query_v266, '2.66', '2.72')
@validation.query_schema(schema.query_params_v273, '2.73', '2.74') @validation.query_schema(schema.index_query_v273, '2.73', '2.74')
@validation.query_schema(schema.query_params_v275, '2.75') @validation.query_schema(schema.index_query_v275, '2.75')
@validation.response_body_schema(schema.index_response, '2.1', '2.68') @validation.response_body_schema(schema.index_response, '2.1', '2.68')
@validation.response_body_schema(schema.index_response_v269, '2.69') @validation.response_body_schema(schema.index_response_v269, '2.69')
def index(self, req): def index(self, req):
@@ -132,11 +132,11 @@ class ServersController(wsgi.Controller):
return servers return servers
@wsgi.expected_errors((400, 403)) @wsgi.expected_errors((400, 403))
@validation.query_schema(schema.query_params_v21, '2.1', '2.25') @validation.query_schema(schema.index_query, '2.1', '2.25')
@validation.query_schema(schema.query_params_v226, '2.26', '2.65') @validation.query_schema(schema.index_query_v226, '2.26', '2.65')
@validation.query_schema(schema.query_params_v266, '2.66', '2.72') @validation.query_schema(schema.index_query_v266, '2.66', '2.72')
@validation.query_schema(schema.query_params_v273, '2.73', '2.74') @validation.query_schema(schema.index_query_v273, '2.73', '2.74')
@validation.query_schema(schema.query_params_v275, '2.75') @validation.query_schema(schema.index_query_v275, '2.75')
@validation.response_body_schema(schema.detail_response, '2.1', '2.2') @validation.response_body_schema(schema.detail_response, '2.1', '2.2')
@validation.response_body_schema(schema.detail_response_v23, '2.3', '2.8') @validation.response_body_schema(schema.detail_response_v23, '2.3', '2.8')
@validation.response_body_schema(schema.detail_response_v29, '2.9', '2.15') @validation.response_body_schema(schema.detail_response_v29, '2.9', '2.15')
@@ -474,7 +474,8 @@ class ServersController(wsgi.Controller):
return objects.NetworkRequestList(objects=networks) return objects.NetworkRequestList(objects=networks)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query, '2.1', '2.101')
@validation.query_schema(schema.show_query_v2102, '2.102')
@validation.response_body_schema(schema.show_response, '2.0', '2.2') @validation.response_body_schema(schema.show_response, '2.0', '2.2')
@validation.response_body_schema(schema.show_response_v23, '2.3', '2.8') @validation.response_body_schema(schema.show_response_v23, '2.3', '2.8')
@validation.response_body_schema(schema.show_response_v29, '2.9', '2.15') @validation.response_body_schema(schema.show_response_v29, '2.9', '2.15')
@@ -106,17 +106,14 @@ class AggregateTestCaseV21(test.NoDBTestCase):
set_metadata = 'self.controller._set_metadata' set_metadata = 'self.controller._set_metadata'
bad_request = exception.ValidationError bad_request = exception.ValidationError
def _set_up(self): def setUp(self):
super().setUp()
self.controller = aggregates_v21.AggregateController() self.controller = aggregates_v21.AggregateController()
self.req = fakes.HTTPRequest.blank('/v2.1/os-aggregates', self.req = fakes.HTTPRequest.blank('/v2.1/os-aggregates',
use_admin_context=True) use_admin_context=True)
self.user_req = fakes.HTTPRequest.blank('/v2.1/os-aggregates') self.user_req = fakes.HTTPRequest.blank('/v2.1/os-aggregates')
self.context = self.req.environ['nova.context'] self.context = self.req.environ['nova.context']
def setUp(self):
super(AggregateTestCaseV21, self).setUp()
self._set_up()
def test_index(self): def test_index(self):
def _list_aggregates(context): def _list_aggregates(context):
if context is None: if context is None:
@@ -130,6 +127,15 @@ class AggregateTestCaseV21(test.NoDBTestCase):
self._assert_agg_data(AGGREGATE_LIST, _make_agg_list(result)) self._assert_agg_data(AGGREGATE_LIST, _make_agg_list(result))
self.assertTrue(mock_list.called) self.assertTrue(mock_list.called)
def test_index_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
'/v2/os-aggregates?unknown=1',
use_admin_context=True,
version='2.102')
self.assertRaises(
exception.ValidationError, self.controller.index, req
)
def test_create(self): def test_create(self):
with mock.patch.object(self.controller.api, 'create_aggregate', with mock.patch.object(self.controller.api, 'create_aggregate',
return_value=AGGREGATE) as mock_create: return_value=AGGREGATE) as mock_create:
@@ -293,6 +299,15 @@ class AggregateTestCaseV21(test.NoDBTestCase):
self._assert_agg_data(AGGREGATE, _make_agg_obj(aggregate)) self._assert_agg_data(AGGREGATE, _make_agg_obj(aggregate))
mock_get.assert_called_once_with(self.context, '1') mock_get.assert_called_once_with(self.context, '1')
def test_show_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
'/v2/os-aggregates/1?unknown=1',
use_admin_context=True,
version='2.102')
self.assertRaises(
exception.ValidationError, self.controller.show, req, '1'
)
def test_show_with_bad_aggregate(self): def test_show_with_bad_aggregate(self):
side_effect = exception.AggregateNotFound(aggregate_id='2') side_effect = exception.AggregateNotFound(aggregate_id='2')
with mock.patch.object(self.controller.api, 'get_aggregate', with mock.patch.object(self.controller.api, 'get_aggregate',
@@ -184,6 +184,29 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
self.attachments.show, self.req, FAKE_UUID1, self.attachments.show, self.req, FAKE_UUID1,
FAKE_PORT_ID1) FAKE_PORT_ID1)
def test_show_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
f'/servers/{FAKE_UUID1}/os-interface/{FAKE_PORT_ID1}?invalid=1',
use_admin_context=True, version='2.102')
self.assertRaises(
exception.ValidationError,
self.attachments.show,
req,
FAKE_UUID1,
FAKE_PORT_ID1,
)
def test_index_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
f'/servers/{FAKE_UUID1}/os-interface?invalid=1',
use_admin_context=True, version='2.102')
self.assertRaises(
exception.ValidationError,
self.attachments.index,
req,
FAKE_UUID1,
)
def test_delete(self): def test_delete(self):
self.stub_out('nova.compute.api.API.detach_interface', self.stub_out('nova.compute.api.API.detach_interface',
fake_detach_interface) fake_detach_interface)
@@ -128,6 +128,14 @@ class AvailabilityZoneApiTestV21(test.NoDBTestCase):
self.assertFalse(zones[1]['zoneState']['available']) self.assertFalse(zones[1]['zoneState']['available'])
self.assertIsNone(zones[1]['hosts']) self.assertIsNone(zones[1]['hosts'])
def test_availability_zone_index_invalid_query_params(self):
req = fakes.HTTPRequest.blank('?invalid=1', version='2.102')
self.assertRaises(
exception.ValidationError,
self.controller.index,
req,
)
def test_availability_zone_detail(self): def test_availability_zone_detail(self):
req = fakes.HTTPRequest.blank('') req = fakes.HTTPRequest.blank('')
resp_dict = self.controller.detail(req) resp_dict = self.controller.detail(req)
@@ -191,6 +199,14 @@ class AvailabilityZoneApiTestV21(test.NoDBTestCase):
self.assertThat(resp_dict, self.assertThat(resp_dict,
matchers.DictMatches(expected_response)) matchers.DictMatches(expected_response))
def test_availability_zone_detail_invalid_query_params(self):
req = fakes.HTTPRequest.blank('?invalid=1', version='2.102')
self.assertRaises(
exception.ValidationError,
self.controller.index,
req,
)
class ServersControllerCreateTestV21(test.TestCase): class ServersControllerCreateTestV21(test.TestCase):
base_url = '/v2.1' base_url = '/v2.1'
@@ -15,6 +15,7 @@
import webob import webob
from nova.api.openstack.compute import extension_info from nova.api.openstack.compute import extension_info
from nova import exception
from nova import test from nova import test
from nova.tests.unit.api.openstack import fakes from nova.tests.unit.api.openstack import fakes
@@ -25,7 +26,24 @@ class ExtensionInfoV21Test(test.NoDBTestCase):
super(ExtensionInfoV21Test, self).setUp() super(ExtensionInfoV21Test, self).setUp()
self.controller = extension_info.ExtensionInfoController() self.controller = extension_info.ExtensionInfoController()
def test_extension_info_show_servers_not_present(self): def test_extension_info_index_invalid_query_params(self):
req = fakes.HTTPRequest.blank('?invalid=1', version='2.102')
self.assertRaises(
exception.ValidationError,
self.controller.index,
req,
)
def test_extension_info_show_extension_not_present(self):
req = fakes.HTTPRequest.blank('/extensions/servers') req = fakes.HTTPRequest.blank('/extensions/servers')
self.assertRaises(webob.exc.HTTPNotFound, self.controller.show, self.assertRaises(webob.exc.HTTPNotFound, self.controller.show,
req, 'servers') req, 'servers')
def test_extension_info_show_invalid_query_params(self):
req = fakes.HTTPRequest.blank('?invalid=1', version='2.102')
self.assertRaises(
exception.ValidationError,
self.controller.show,
req,
'servers',
)
@@ -839,8 +839,7 @@ class FlavorsTestV275(FlavorsTestV261):
@mock.patch('nova.objects.Flavor.get_by_flavor_id') @mock.patch('nova.objects.Flavor.get_by_flavor_id')
def test_show_flavor_default_swap_value_old_version(self, mock_get): def test_show_flavor_default_swap_value_old_version(self, mock_get):
mock_get.return_value = self.FLAVOR_WITH_NO_SWAP mock_get.return_value = self.FLAVOR_WITH_NO_SWAP
req = fakes.HTTPRequestV21.blank( req = fakes.HTTPRequestV21.blank('/flavors/1', version='2.74')
'/flavors/detail?limit=1', version='2.74')
response = self.controller.show(req, 1) response = self.controller.show(req, 1)
response_list = response["flavor"] response_list = response["flavor"]
self.assertEqual(response_list['swap'], "") self.assertEqual(response_list['swap'], "")
@@ -859,7 +858,7 @@ class FlavorsTestV275(FlavorsTestV261):
def test_show_flavor_default_swap_value(self, mock_get): def test_show_flavor_default_swap_value(self, mock_get):
mock_get.return_value = self.FLAVOR_WITH_NO_SWAP mock_get.return_value = self.FLAVOR_WITH_NO_SWAP
req = fakes.HTTPRequestV21.blank( req = fakes.HTTPRequestV21.blank(
'/flavors/detail?limit=1', version=self.microversion) '/flavors/1', version=self.microversion)
response = self.controller.show(req, 1) response = self.controller.show(req, 1)
response_list = response["flavor"] response_list = response["flavor"]
self.assertEqual(response_list['swap'], 0) self.assertEqual(response_list['swap'], 0)
@@ -943,6 +942,33 @@ class FlavorsTestV2102(FlavorsTestV275):
self.omit_legacy_fields = False self.omit_legacy_fields = False
super().test_list_detail_flavors_with_additional_filter_old_version() super().test_list_detail_flavors_with_additional_filter_old_version()
def test_list_flavor_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
'/flavors?unknown=1',
use_admin_context=True,
version='2.102')
self.assertRaises(
exception.ValidationError, self.controller.index, req
)
def test_detail_flavor_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
'/flavors/detail?unknown=1',
use_admin_context=True,
version='2.102')
self.assertRaises(
exception.ValidationError, self.controller.detail, req
)
def test_show_flavor_invalid_query_params(self):
req = fakes.HTTPRequest.blank(
'/flavors/123?unknown=1',
use_admin_context=True,
version='2.102')
self.assertRaises(
exception.ValidationError, self.controller.show, req, '123'
)
class DisabledFlavorsWithRealDBTestV21(test.TestCase): class DisabledFlavorsWithRealDBTestV21(test.TestCase):
"""Tests that disabled flavors should not be shown nor listed.""" """Tests that disabled flavors should not be shown nor listed."""
@@ -8,4 +8,6 @@ features:
In addition, the ``rxtx_factor`` and ``OS-FLV-DISABLED:disabled`` fields In addition, the ``rxtx_factor`` and ``OS-FLV-DISABLED:disabled`` fields
have been removed from all flavors responses, while the ``rxtx_factor`` have been removed from all flavors responses, while the ``rxtx_factor``
field can no longer be provided when creating a server. field can no longer be provided when creating a server. Finally, all APIs
now reject unknown query string parameters with a HTTP 400 (Bad Request)
error, building upon work first started in microversion 2.75.