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 c6228d81dfbf..636e08df8a78 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 f8d8082e9b51..44b83a1cef57 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 1532e37c1313..8f239f623aea 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 45fc7f9f6d89..fc19e0faef0c 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 924b816b4b25..22470cf148dc 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, @@ -957,7 +973,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'] @@ -984,8 +1001,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 @@ -1033,14 +1054,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 4d779ea31ac6..01facad97822 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]*(?