From 4354163c1e149ffdff5f46b9f480d58704f009c8 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Thu, 10 Sep 2015 11:02:15 +0800 Subject: [PATCH] Filter leading/trailing spaces for name field in v2.1 compat mode In the V2 API, there are three cases for the name field: 1. disallow any space in the name: server_groups. 2. allow leading/trailing whitespaces, strip spaces and disallow all characters are spaces: flavor_manage, servers. 3. allow leading/trailing whitespacess, no strip spaces and allow all characters are spaces: aggregates, cells, create_backup, security_groups, create_image, rebuild But currently in the V2.1 API and V2.1 API compat mode disallows leading/trailing in the name field. For the V2.1 compat mode, we should relax the validation to avoid breaking the user, although leading/trailing is unclear usecase. This patch allows leading/trailing spaces but will strip them, and still disallows that all characters are spaces in the name fields for flavor_mange, servers, aggregates(and availability_zones), create_backup, create_image, rebuild. Due to the server_groups and security_groups(no jsons-schema in v2.1) have consistent behavior between v2 and v2.1. So this patch won't change server_groups. But when creating servers, the name of security_groups, availability_zone and keyapir isn't stripped the leading/trailing spaces. This is for backward compatible with users who already use legacy V2 API created security_group, availabilit_zone and keypair with leading/trailing in the name, otherwise the users can't use those resource anymore. For supporting servers schema extension point returns legacy v2 schema, this patch adds version parameter to the schema extension point. Then extension point can return different schema based on the version parameter. Change-Id: I9442891272284d395ea0dd8cfa302d3f74bf13ec Partial-Bug: #1498075 --- nova/api/openstack/common.py | 11 ++ nova/api/openstack/compute/access_ips.py | 2 +- nova/api/openstack/compute/aggregates.py | 13 +- .../openstack/compute/availability_zone.py | 8 +- .../openstack/compute/block_device_mapping.py | 2 +- .../compute/block_device_mapping_v1.py | 2 +- nova/api/openstack/compute/cells.py | 9 +- nova/api/openstack/compute/config_drive.py | 2 +- nova/api/openstack/compute/create_backup.py | 5 +- nova/api/openstack/compute/disk_config.py | 2 +- nova/api/openstack/compute/flavor_manage.py | 3 +- nova/api/openstack/compute/keypairs.py | 17 ++- nova/api/openstack/compute/multiple_create.py | 2 +- nova/api/openstack/compute/personality.py | 2 +- .../compute/preserve_ephemeral_rebuild.py | 2 +- nova/api/openstack/compute/scheduler_hints.py | 2 +- .../openstack/compute/schemas/aggregates.py | 22 ++- .../compute/schemas/availability_zone.py | 4 + nova/api/openstack/compute/schemas/cells.py | 12 ++ .../compute/schemas/create_backup.py | 8 + .../compute/schemas/flavor_manage.py | 7 + .../api/openstack/compute/schemas/keypairs.py | 12 ++ .../compute/schemas/security_groups.py | 7 + nova/api/openstack/compute/schemas/servers.py | 26 ++++ nova/api/openstack/compute/security_groups.py | 4 +- nova/api/openstack/compute/servers.py | 64 +++++--- nova/api/openstack/compute/user_data.py | 2 +- nova/api/validation/parameter_types.py | 50 ++++++- .../compute/legacy_v2/test_servers.py | 54 +++++++ .../api/openstack/compute/test_aggregates.py | 81 +++++++++++ .../unit/api/openstack/compute/test_cells.py | 41 ++++++ .../openstack/compute/test_create_backup.py | 56 +++++++ .../openstack/compute/test_flavor_manage.py | 24 ++- .../api/openstack/compute/test_keypairs.py | 96 ++++++++++++ .../api/openstack/compute/test_serversV21.py | 137 +++++++++++++++++- nova/tests/unit/test_api_validation.py | 78 ++++++++++ 36 files changed, 810 insertions(+), 59 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 19e0f14ef631..a604e6847795 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -512,6 +512,17 @@ def get_instance(compute_api, context, instance_id, expected_attrs=None): raise exc.HTTPNotFound(explanation=e.format_message()) +def normalize_name(name): + # NOTE(alex_xu): This method is used by v2.1 legacy v2 compat mode. + # In the legacy v2 API, some of APIs strip the spaces and some of APIs not. + # The v2.1 disallow leading/trailing, for compatible v2 API and consistent, + # we enable leading/trailing spaces and strip spaces in legacy v2 compat + # mode. Althrough in legacy v2 API there are some APIs didn't strip spaces, + # but actually leading/trailing spaces(that means user depend on leading/ + # trailing spaces distinguish different instance) is pointless usecase. + return name.strip() + + def raise_feature_not_supported(msg=None): if msg is None: msg = _("The requested functionality is not supported.") diff --git a/nova/api/openstack/compute/access_ips.py b/nova/api/openstack/compute/access_ips.py index 0dfdd697a58d..f55978cd29ca 100644 --- a/nova/api/openstack/compute/access_ips.py +++ b/nova/api/openstack/compute/access_ips.py @@ -98,7 +98,7 @@ class AccessIPs(extensions.V21APIExtensionBase): server_update = server_create server_rebuild = server_create - def get_server_create_schema(self): + def get_server_create_schema(self, version): return access_ips.server_create get_server_update_schema = get_server_create_schema diff --git a/nova/api/openstack/compute/aggregates.py b/nova/api/openstack/compute/aggregates.py index 8ee13c2a632e..d9f46279e18d 100644 --- a/nova/api/openstack/compute/aggregates.py +++ b/nova/api/openstack/compute/aggregates.py @@ -19,6 +19,7 @@ import datetime from webob import exc +from nova.api.openstack import common from nova.api.openstack.compute.schemas import aggregates from nova.api.openstack import extensions from nova.api.openstack import wsgi @@ -52,7 +53,8 @@ class AggregateController(wsgi.Controller): # NOTE(gmann): Returns 200 for backwards compatibility but should be 201 # as this operation complete the creation of aggregates resource. @extensions.expected_errors((400, 409)) - @validation.schema(aggregates.create) + @validation.schema(aggregates.create_v20, '2.0', '2.0') + @validation.schema(aggregates.create, '2.1') def create(self, req, body): """Creates an aggregate, given its name and optional availability zone. @@ -60,8 +62,10 @@ class AggregateController(wsgi.Controller): context = _get_context(req) authorize(context, action='create') host_aggregate = body["aggregate"] - name = host_aggregate["name"] + name = common.normalize_name(host_aggregate["name"]) avail_zone = host_aggregate.get("availability_zone") + if avail_zone: + avail_zone = common.normalize_name(avail_zone) try: aggregate = self.api.create_aggregate(context, name, avail_zone) @@ -91,12 +95,15 @@ class AggregateController(wsgi.Controller): return self._marshall_aggregate(aggregate) @extensions.expected_errors((400, 404, 409)) - @validation.schema(aggregates.update) + @validation.schema(aggregates.update_v20, '2.0', '2.0') + @validation.schema(aggregates.update, '2.1') def update(self, req, id, body): """Updates the name and/or availability_zone of given aggregate.""" context = _get_context(req) authorize(context, action='update') updates = body["aggregate"] + if 'name' in updates: + updates['name'] = common.normalize_name(updates['name']) try: aggregate = self.api.update_aggregate(context, id, updates) diff --git a/nova/api/openstack/compute/availability_zone.py b/nova/api/openstack/compute/availability_zone.py index 4638ffdd3605..324c816f3d3b 100644 --- a/nova/api/openstack/compute/availability_zone.py +++ b/nova/api/openstack/compute/availability_zone.py @@ -139,7 +139,13 @@ class AvailabilityZone(extensions.V21APIExtensionBase): # NOTE(gmann): This function is not supposed to use 'body_deprecated_param' # parameter as this is placed to handle scheduler_hint extension for V2.1. def server_create(self, server_dict, create_kwargs, body_deprecated_param): + # NOTE(alex_xu): For v2.1 compat mode, we strip the spaces when create + # availability_zone. But we don't strip at here for backward-compatible + # with some users already created availability_zone with + # leading/trailing spaces with legacy v2 API. create_kwargs['availability_zone'] = server_dict.get(ATTRIBUTE_NAME) - def get_server_create_schema(self): + def get_server_create_schema(self, version): + if version == "2.0": + return schema.server_create_v20 return schema.server_create diff --git a/nova/api/openstack/compute/block_device_mapping.py b/nova/api/openstack/compute/block_device_mapping.py index 1c209ab98d6d..a449abe06149 100644 --- a/nova/api/openstack/compute/block_device_mapping.py +++ b/nova/api/openstack/compute/block_device_mapping.py @@ -73,5 +73,5 @@ class BlockDeviceMapping(extensions.V21APIExtensionBase): # Unset the legacy_bdm flag if we got a block device mapping. create_kwargs['legacy_bdm'] = False - def get_server_create_schema(self): + def get_server_create_schema(self, version): return schema_block_device_mapping.server_create diff --git a/nova/api/openstack/compute/block_device_mapping_v1.py b/nova/api/openstack/compute/block_device_mapping_v1.py index ad8537f3e6c2..ee38bceae79d 100644 --- a/nova/api/openstack/compute/block_device_mapping_v1.py +++ b/nova/api/openstack/compute/block_device_mapping_v1.py @@ -64,5 +64,5 @@ class BlockDeviceMappingV1(extensions.V21APIExtensionBase): # Sets the legacy_bdm flag if we got a legacy block device mapping. create_kwargs['legacy_bdm'] = True - def get_server_create_schema(self): + def get_server_create_schema(self, version): return schema_block_device_mapping.server_create diff --git a/nova/api/openstack/compute/cells.py b/nova/api/openstack/compute/cells.py index 5b9d1e430826..fd2b8026d06b 100644 --- a/nova/api/openstack/compute/cells.py +++ b/nova/api/openstack/compute/cells.py @@ -194,6 +194,9 @@ class CellsController(wsgi.Controller): * Merging existing transport URL with transport information. """ + if 'name' in cell: + cell['name'] = common.normalize_name(cell['name']) + # Start with the cell type conversion if 'type' in cell: cell['is_parent'] = cell['type'] == 'parent' @@ -236,7 +239,8 @@ class CellsController(wsgi.Controller): # returning a response. @extensions.expected_errors((400, 403, 501)) @common.check_cells_enabled - @validation.schema(cells.create) + @validation.schema(cells.create_v20, '2.0', '2.0') + @validation.schema(cells.create, '2.1') def create(self, req, body): """Create a child cell entry.""" context = req.environ['nova.context'] @@ -253,7 +257,8 @@ class CellsController(wsgi.Controller): @extensions.expected_errors((400, 403, 404, 501)) @common.check_cells_enabled - @validation.schema(cells.update) + @validation.schema(cells.update_v20, '2.0', '2.0') + @validation.schema(cells.update, '2.1') def update(self, req, id, body): """Update a child cell entry. 'id' is the cell name to update.""" context = req.environ['nova.context'] diff --git a/nova/api/openstack/compute/config_drive.py b/nova/api/openstack/compute/config_drive.py index a1cd87e820f5..f4aa4b542cdf 100644 --- a/nova/api/openstack/compute/config_drive.py +++ b/nova/api/openstack/compute/config_drive.py @@ -73,5 +73,5 @@ class ConfigDrive(extensions.V21APIExtensionBase): def server_create(self, server_dict, create_kwargs, body_deprecated_param): create_kwargs['config_drive'] = server_dict.get(ATTRIBUTE_NAME) - def get_server_create_schema(self): + def get_server_create_schema(self, version): return schema_config_drive.server_create diff --git a/nova/api/openstack/compute/create_backup.py b/nova/api/openstack/compute/create_backup.py index 1d81bb8c6f08..458165bb3904 100644 --- a/nova/api/openstack/compute/create_backup.py +++ b/nova/api/openstack/compute/create_backup.py @@ -36,7 +36,8 @@ class CreateBackupController(wsgi.Controller): @extensions.expected_errors((400, 403, 404, 409)) @wsgi.action('createBackup') - @validation.schema(create_backup.create_backup) + @validation.schema(create_backup.create_backup_v20, '2.0', '2.0') + @validation.schema(create_backup.create_backup, '2.1') def _create_backup(self, req, id, body): """Backup a server instance. @@ -52,7 +53,7 @@ class CreateBackupController(wsgi.Controller): authorize(context) entity = body["createBackup"] - image_name = entity["name"] + image_name = common.normalize_name(entity["name"]) backup_type = entity["backup_type"] rotation = int(entity["rotation"]) diff --git a/nova/api/openstack/compute/disk_config.py b/nova/api/openstack/compute/disk_config.py index 2fc692edefa2..171a0952af0a 100644 --- a/nova/api/openstack/compute/disk_config.py +++ b/nova/api/openstack/compute/disk_config.py @@ -146,7 +146,7 @@ class DiskConfig(extensions.V21APIExtensionBase): server_rebuild = server_create server_resize = server_create - def get_server_create_schema(self): + def get_server_create_schema(self, version): return disk_config.server_create get_server_update_schema = get_server_create_schema diff --git a/nova/api/openstack/compute/flavor_manage.py b/nova/api/openstack/compute/flavor_manage.py index 11fac9ba9bb6..960f091c8bf3 100644 --- a/nova/api/openstack/compute/flavor_manage.py +++ b/nova/api/openstack/compute/flavor_manage.py @@ -54,7 +54,8 @@ class FlavorManageController(wsgi.Controller): # as this operation complete the creation of flavor resource. @wsgi.action("create") @extensions.expected_errors((400, 409, 500)) - @validation.schema(flavor_manage.create) + @validation.schema(flavor_manage.create_v20, '2.0', '2.0') + @validation.schema(flavor_manage.create, '2.1') def _create(self, req, body): context = req.environ['nova.context'] authorize(context) diff --git a/nova/api/openstack/compute/keypairs.py b/nova/api/openstack/compute/keypairs.py index 17d33f66a937..7f646d119076 100644 --- a/nova/api/openstack/compute/keypairs.py +++ b/nova/api/openstack/compute/keypairs.py @@ -18,6 +18,7 @@ import webob import webob.exc +from nova.api.openstack import common from nova.api.openstack.compute.schemas import keypairs from nova.api.openstack import extensions from nova.api.openstack import wsgi @@ -93,7 +94,8 @@ class KeypairController(wsgi.Controller): @wsgi.Controller.api_version("2.1", "2.1") # noqa @extensions.expected_errors((400, 403, 409)) - @validation.schema(keypairs.create) + @validation.schema(keypairs.create_v20, "2.0", "2.0") + @validation.schema(keypairs.create, "2.1", "2.1") def create(self, req, body): """Create or import keypair. @@ -111,7 +113,7 @@ class KeypairController(wsgi.Controller): def _create(self, req, body, user_id=None, **keypair_filters): context = req.environ['nova.context'] params = body['keypair'] - name = params['name'] + name = common.normalize_name(params['name']) key_type = params.get('type', keypair_obj.KEYPAIR_TYPE_SSH) user_id = user_id or context.user_id authorize(context, action='create', @@ -304,7 +306,14 @@ class Keypairs(extensions.V21APIExtensionBase): # NOTE(gmann): This function is not supposed to use 'body_deprecated_param' # parameter as this is placed to handle scheduler_hint extension for V2.1. def server_create(self, server_dict, create_kwargs, body_deprecated_param): + # NOTE(alex_xu): The v2.1 API compat mode, we strip the spaces for + # keypair create. But we didn't strip spaces at here for + # backward-compatible some users already created keypair and name with + # leading/trailing spaces by legacy v2 API. create_kwargs['key_name'] = server_dict.get('key_name') - def get_server_create_schema(self): - return keypairs.server_create + def get_server_create_schema(self, version): + if version == '2.0': + return keypairs.server_create_v20 + else: + return keypairs.server_create diff --git a/nova/api/openstack/compute/multiple_create.py b/nova/api/openstack/compute/multiple_create.py index 60733497a390..66f42db3aac4 100644 --- a/nova/api/openstack/compute/multiple_create.py +++ b/nova/api/openstack/compute/multiple_create.py @@ -60,5 +60,5 @@ class MultipleCreate(extensions.V21APIExtensionBase): create_kwargs['max_count'] = max_count create_kwargs['return_reservation_id'] = return_id - def get_server_create_schema(self): + def get_server_create_schema(self, version): return schema_multiple_create.server_create diff --git a/nova/api/openstack/compute/personality.py b/nova/api/openstack/compute/personality.py index d3c6be343a1b..2d0aecfe218a 100644 --- a/nova/api/openstack/compute/personality.py +++ b/nova/api/openstack/compute/personality.py @@ -59,7 +59,7 @@ class Personality(extensions.V21APIExtensionBase): create_kwargs['files_to_inject'] = self._get_injected_files( server_dict['personality']) - def get_server_create_schema(self): + def get_server_create_schema(self, version): return personality.server_create get_server_rebuild_schema = get_server_create_schema diff --git a/nova/api/openstack/compute/preserve_ephemeral_rebuild.py b/nova/api/openstack/compute/preserve_ephemeral_rebuild.py index be1dc99361e6..78e30ccc01ca 100644 --- a/nova/api/openstack/compute/preserve_ephemeral_rebuild.py +++ b/nova/api/openstack/compute/preserve_ephemeral_rebuild.py @@ -40,5 +40,5 @@ class PreserveEphemeralRebuild(extensions.V21APIExtensionBase): rebuild_kwargs['preserve_ephemeral'] = strutils.bool_from_string( rebuild_dict['preserve_ephemeral'], strict=True) - def get_server_rebuild_schema(self): + def get_server_rebuild_schema(self, version): return preserve_ephemeral_rebuild.server_rebuild diff --git a/nova/api/openstack/compute/scheduler_hints.py b/nova/api/openstack/compute/scheduler_hints.py index 26754996b931..35f446c7561c 100644 --- a/nova/api/openstack/compute/scheduler_hints.py +++ b/nova/api/openstack/compute/scheduler_hints.py @@ -44,5 +44,5 @@ class SchedulerHints(extensions.V21APIExtensionBase): create_kwargs['scheduler_hints'] = scheduler_hints - def get_server_create_schema(self): + def get_server_create_schema(self, version): return schema.server_create diff --git a/nova/api/openstack/compute/schemas/aggregates.py b/nova/api/openstack/compute/schemas/aggregates.py index 3d89a9626454..52f0a6878357 100644 --- a/nova/api/openstack/compute/schemas/aggregates.py +++ b/nova/api/openstack/compute/schemas/aggregates.py @@ -18,6 +18,10 @@ from nova.api.validation import parameter_types availability_zone = copy.deepcopy(parameter_types.name) availability_zone['type'] = ['string', 'null'] +availability_zone_with_leading_trailing_spaces = copy.deepcopy(parameter_types. + name_with_leading_trailing_spaces) +availability_zone_with_leading_trailing_spaces['type'] = ['string', 'null'] + create = { 'type': 'object', @@ -37,6 +41,14 @@ create = { 'additionalProperties': False, } + +create_v20 = copy.deepcopy(create) +create_v20['properties']['aggregate']['properties']['name'] = (parameter_types. + name_with_leading_trailing_spaces) +create_v20['properties']['aggregate']['properties']['availability_zone'] = ( + availability_zone_with_leading_trailing_spaces) + + update = { 'type': 'object', 'properties': { @@ -44,7 +56,7 @@ update = { 'aggregate': { 'type': 'object', 'properties': { - 'name': parameter_types.name, + 'name': parameter_types.name_with_leading_trailing_spaces, 'availability_zone': availability_zone }, 'additionalProperties': False, @@ -58,6 +70,14 @@ update = { 'additionalProperties': False, } + +update_v20 = copy.deepcopy(update) +update_v20['properties']['aggregate']['properties']['name'] = (parameter_types. + name_with_leading_trailing_spaces) +update_v20['properties']['aggregate']['properties']['availability_zone'] = ( + availability_zone_with_leading_trailing_spaces) + + add_host = { 'type': 'object', 'properties': { diff --git a/nova/api/openstack/compute/schemas/availability_zone.py b/nova/api/openstack/compute/schemas/availability_zone.py index d454fc9ff6ad..742a2f653693 100644 --- a/nova/api/openstack/compute/schemas/availability_zone.py +++ b/nova/api/openstack/compute/schemas/availability_zone.py @@ -17,3 +17,7 @@ from nova.api.validation import parameter_types server_create = { 'availability_zone': parameter_types.name, } + +server_create_v20 = { + 'availability_zone': parameter_types.name_with_leading_trailing_spaces, +} diff --git a/nova/api/openstack/compute/schemas/cells.py b/nova/api/openstack/compute/schemas/cells.py index 2ee79e32b4c5..1115aeaf7cc7 100644 --- a/nova/api/openstack/compute/schemas/cells.py +++ b/nova/api/openstack/compute/schemas/cells.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types @@ -55,6 +57,11 @@ create = { } +create_v20 = copy.deepcopy(create) +create_v20['properties']['cell']['properties']['name'] = (parameter_types. + cell_name_leading_trailing_spaces) + + update = { 'type': 'object', 'properties': { @@ -85,6 +92,11 @@ update = { } +update_v20 = copy.deepcopy(create) +update_v20['properties']['cell']['properties']['name'] = (parameter_types. + cell_name_leading_trailing_spaces) + + sync_instances = { 'type': 'object', 'properties': { diff --git a/nova/api/openstack/compute/schemas/create_backup.py b/nova/api/openstack/compute/schemas/create_backup.py index 1566ea30878d..452ac78acace 100644 --- a/nova/api/openstack/compute/schemas/create_backup.py +++ b/nova/api/openstack/compute/schemas/create_backup.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types @@ -37,3 +39,9 @@ create_backup = { 'required': ['createBackup'], 'additionalProperties': False, } + + +create_backup_v20 = copy.deepcopy(create_backup) +create_backup_v20['properties'][ + 'createBackup']['properties']['name'] = (parameter_types. + name_with_leading_trailing_spaces) diff --git a/nova/api/openstack/compute/schemas/flavor_manage.py b/nova/api/openstack/compute/schemas/flavor_manage.py index 8ef485c2534c..127e1a5d8b3c 100644 --- a/nova/api/openstack/compute/schemas/flavor_manage.py +++ b/nova/api/openstack/compute/schemas/flavor_manage.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types create = { @@ -56,3 +58,8 @@ create = { 'required': ['flavor'], 'additionalProperties': False, } + + +create_v20 = copy.deepcopy(create) +create_v20['properties']['flavor']['properties']['name'] = (parameter_types. + name_with_leading_trailing_spaces) diff --git a/nova/api/openstack/compute/schemas/keypairs.py b/nova/api/openstack/compute/schemas/keypairs.py index f8a057569207..22868769daee 100644 --- a/nova/api/openstack/compute/schemas/keypairs.py +++ b/nova/api/openstack/compute/schemas/keypairs.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types @@ -32,6 +34,12 @@ create = { 'additionalProperties': False, } + +create_v20 = copy.deepcopy(create) +create_v20['properties']['keypair']['properties']['name'] = (parameter_types. + name_with_leading_trailing_spaces) + + create_v22 = { 'type': 'object', 'properties': { @@ -78,3 +86,7 @@ create_v210 = { server_create = { 'key_name': parameter_types.name, } + +server_create_v20 = { + 'key_name': parameter_types.name_with_leading_trailing_spaces, +} diff --git a/nova/api/openstack/compute/schemas/security_groups.py b/nova/api/openstack/compute/schemas/security_groups.py index f6f899ca9702..a8df55e002a3 100644 --- a/nova/api/openstack/compute/schemas/security_groups.py +++ b/nova/api/openstack/compute/schemas/security_groups.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types server_create = { @@ -29,3 +31,8 @@ server_create = { } }, } + + +server_create_v20 = copy.deepcopy(server_create) +server_create_v20['security_groups']['items']['properties']['name'] = ( + parameter_types.name_with_leading_trailing_spaces) diff --git a/nova/api/openstack/compute/schemas/servers.py b/nova/api/openstack/compute/schemas/servers.py index c4ea3732553c..43932436004c 100644 --- a/nova/api/openstack/compute/schemas/servers.py +++ b/nova/api/openstack/compute/schemas/servers.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + from nova.api.validation import parameter_types @@ -50,6 +52,12 @@ base_create = { 'additionalProperties': False, } + +base_create_v20 = copy.deepcopy(base_create) +base_create_v20['properties']['server'][ + 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces + + base_update = { 'type': 'object', 'properties': { @@ -65,6 +73,12 @@ base_update = { 'additionalProperties': False, } + +base_update_v20 = copy.deepcopy(base_update) +base_update_v20['properties']['server'][ + 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces + + base_rebuild = { 'type': 'object', 'properties': { @@ -85,6 +99,12 @@ base_rebuild = { 'additionalProperties': False, } + +base_rebuild_v20 = copy.deepcopy(base_rebuild) +base_rebuild_v20['properties']['rebuild'][ + 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces + + base_resize = { 'type': 'object', 'properties': { @@ -118,6 +138,12 @@ create_image = { 'additionalProperties': False } + +create_image_v20 = copy.deepcopy(create_image) +create_image_v20['properties']['createImage'][ + 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces + + reboot = { 'type': 'object', 'properties': { diff --git a/nova/api/openstack/compute/security_groups.py b/nova/api/openstack/compute/security_groups.py index 5ccd213270ae..8f6405d3c754 100644 --- a/nova/api/openstack/compute/security_groups.py +++ b/nova/api/openstack/compute/security_groups.py @@ -527,5 +527,7 @@ class SecurityGroups(extensions.V21APIExtensionBase): create_kwargs['security_group'] = list( set(create_kwargs['security_group'])) - def get_server_create_schema(self): + def get_server_create_schema(self, version): + if version == '2.0': + return schema_security_groups.server_create_v20 return schema_security_groups.server_create diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index aca867c2f255..c7d37adcc6b8 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -82,6 +82,10 @@ class ServersController(wsgi.Controller): schema_server_rebuild = schema_servers.base_rebuild schema_server_resize = schema_servers.base_resize + schema_server_create_v20 = schema_servers.base_create_v20 + schema_server_update_v20 = schema_servers.base_update_v20 + schema_server_rebuild_v20 = schema_servers.base_rebuild_v20 + @staticmethod def _add_location(robj): # Just in case... @@ -201,7 +205,10 @@ class ServersController(wsgi.Controller): propagate_map_exceptions=True) if list(self.create_schema_manager): self.create_schema_manager.map(self._create_extension_schema, - self.schema_server_create) + self.schema_server_create, '2.1') + self.create_schema_manager.map(self._create_extension_schema, + self.schema_server_create_v20, + '2.0') else: LOG.debug("Did not find any server create schemas") @@ -215,7 +222,10 @@ class ServersController(wsgi.Controller): propagate_map_exceptions=True) if list(self.update_schema_manager): self.update_schema_manager.map(self._update_extension_schema, - self.schema_server_update) + self.schema_server_update, '2.1') + self.update_schema_manager.map(self._update_extension_schema, + self.schema_server_update_v20, + '2.0') else: LOG.debug("Did not find any server update schemas") @@ -229,7 +239,10 @@ class ServersController(wsgi.Controller): propagate_map_exceptions=True) if list(self.rebuild_schema_manager): self.rebuild_schema_manager.map(self._rebuild_extension_schema, - self.schema_server_rebuild) + self.schema_server_rebuild, '2.1') + self.rebuild_schema_manager.map(self._rebuild_extension_schema, + self.schema_server_rebuild_v20, + '2.0') else: LOG.debug("Did not find any server rebuild schemas") @@ -243,7 +256,7 @@ class ServersController(wsgi.Controller): propagate_map_exceptions=True) if list(self.resize_schema_manager): self.resize_schema_manager.map(self._resize_extension_schema, - self.schema_server_resize) + self.schema_server_resize, '2.1') else: LOG.debug("Did not find any server resize schemas") @@ -506,14 +519,15 @@ class ServersController(wsgi.Controller): @wsgi.response(202) @extensions.expected_errors((400, 403, 409, 413)) - @validation.schema(schema_server_create) + @validation.schema(schema_server_create_v20, '2.0', '2.0') + @validation.schema(schema_server_create, '2.1') def create(self, req, body): """Creates a new server for a given user.""" context = req.environ['nova.context'] server_dict = body['server'] password = self._get_server_admin_password(server_dict) - name = server_dict['name'] + name = common.normalize_name(server_dict['name']) # Arguments to be passed to instance create function create_kwargs = {} @@ -704,11 +718,11 @@ class ServersController(wsgi.Controller): LOG.debug("Running _update_extension_point for %s", ext.obj) handler.server_update(update_dict, update_kwargs) - def _create_extension_schema(self, ext, create_schema): + def _create_extension_schema(self, ext, create_schema, version): handler = ext.obj LOG.debug("Running _create_extension_schema for %s", ext.obj) - schema = handler.get_server_create_schema() + schema = handler.get_server_create_schema(version) if ext.obj.name == 'SchedulerHints': # NOTE(oomichi): The request parameter position of scheduler-hint # extension is different from the other extensions, so here handles @@ -717,25 +731,25 @@ class ServersController(wsgi.Controller): else: create_schema['properties']['server']['properties'].update(schema) - def _update_extension_schema(self, ext, update_schema): + def _update_extension_schema(self, ext, update_schema, version): handler = ext.obj LOG.debug("Running _update_extension_schema for %s", ext.obj) - schema = handler.get_server_update_schema() + schema = handler.get_server_update_schema(version) update_schema['properties']['server']['properties'].update(schema) - def _rebuild_extension_schema(self, ext, rebuild_schema): + def _rebuild_extension_schema(self, ext, rebuild_schema, version): handler = ext.obj LOG.debug("Running _rebuild_extension_schema for %s", ext.obj) - schema = handler.get_server_rebuild_schema() + schema = handler.get_server_rebuild_schema(version) rebuild_schema['properties']['rebuild']['properties'].update(schema) - def _resize_extension_schema(self, ext, resize_schema): + def _resize_extension_schema(self, ext, resize_schema, version): handler = ext.obj LOG.debug("Running _resize_extension_schema for %s", ext.obj) - schema = handler.get_server_resize_schema() + schema = handler.get_server_resize_schema(version) resize_schema['properties']['resize']['properties'].update(schema) def _delete(self, context, req, instance_uuid): @@ -753,7 +767,8 @@ class ServersController(wsgi.Controller): self.compute_api.delete(context, instance) @extensions.expected_errors((400, 404)) - @validation.schema(schema_server_update) + @validation.schema(schema_server_update_v20, '2.0', '2.0') + @validation.schema(schema_server_update, '2.1') def update(self, req, id, body): """Update server then pass on to version-specific controller.""" @@ -762,7 +777,8 @@ class ServersController(wsgi.Controller): authorize(ctxt, action='update') if 'name' in body['server']: - update_dict['display_name'] = body['server']['name'] + update_dict['display_name'] = common.normalize_name( + body['server']['name']) if list(self.update_extension_manager): self.update_extension_manager.map(self._update_extension_point, @@ -949,7 +965,8 @@ class ServersController(wsgi.Controller): @wsgi.response(202) @extensions.expected_errors((400, 403, 404, 409, 413)) @wsgi.action('rebuild') - @validation.schema(schema_server_rebuild) + @validation.schema(schema_server_rebuild_v20, '2.0', '2.0') + @validation.schema(schema_server_rebuild, '2.1') def _action_rebuild(self, req, id, body): """Rebuild an instance with the given attributes.""" rebuild_dict = body['rebuild'] @@ -976,8 +993,12 @@ class ServersController(wsgi.Controller): for request_attribute, instance_attribute in attr_map.items(): try: - rebuild_kwargs[instance_attribute] = rebuild_dict[ - request_attribute] + if request_attribute == 'name': + rebuild_kwargs[instance_attribute] = common.normalize_name( + rebuild_dict[request_attribute]) + else: + rebuild_kwargs[instance_attribute] = rebuild_dict[ + request_attribute] except (KeyError, TypeError): pass @@ -1023,14 +1044,15 @@ class ServersController(wsgi.Controller): @extensions.expected_errors((400, 403, 404, 409)) @wsgi.action('createImage') @common.check_snapshots_enabled - @validation.schema(schema_servers.create_image) + @validation.schema(schema_servers.create_image, '2.0', '2.0') + @validation.schema(schema_servers.create_image, '2.1') def _action_create_image(self, req, id, body): """Snapshot a server instance.""" context = req.environ['nova.context'] authorize(context, action='create_image') entity = body["createImage"] - image_name = entity["name"] + image_name = common.normalize_name(entity["name"]) metadata = entity.get('metadata', {}) common.check_img_metadata_properties_quota(context, metadata) diff --git a/nova/api/openstack/compute/user_data.py b/nova/api/openstack/compute/user_data.py index af86b89824c0..fdcb1c0f7a4d 100644 --- a/nova/api/openstack/compute/user_data.py +++ b/nova/api/openstack/compute/user_data.py @@ -38,5 +38,5 @@ class UserData(extensions.V21APIExtensionBase): def server_create(self, server_dict, create_kwargs, body_deprecated_param): create_kwargs['user_data'] = server_dict.get(ATTRIBUTE_NAME) - def get_server_create_schema(self): + def get_server_create_schema(self, version): return schema_user_data.server_create diff --git a/nova/api/validation/parameter_types.py b/nova/api/validation/parameter_types.py index 4fbf6477a3d4..3659d284ca48 100644 --- a/nova/api/validation/parameter_types.py +++ b/nova/api/validation/parameter_types.py @@ -58,16 +58,48 @@ def _get_printable(exclude=None): _printable_ws = ''.join(c for c in _get_all_chars() if unicodedata.category(c) == "Zs") -valid_name_regex = '^(?![%s])[%s]*(?