Merge "Filter leading/trailing spaces for name field in v2.1 compat mode"
This commit is contained in:
commit
10ac88f2b4
|
@ -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.")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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': {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]*(?<![%s])$' % (
|
||||
|
||||
def _get_printable_no_ws(exclude=None):
|
||||
if exclude is None:
|
||||
exclude = []
|
||||
return ''.join(c for c in _get_all_chars()
|
||||
if _is_printable(c) and
|
||||
unicodedata.category(c) != "Zs" and
|
||||
c not in exclude)
|
||||
|
||||
valid_name_regex_base = '^(?![%s])[%s]*(?<![%s])$'
|
||||
|
||||
|
||||
valid_name_regex = valid_name_regex_base % (
|
||||
re.escape(_printable_ws), re.escape(_get_printable()),
|
||||
re.escape(_printable_ws))
|
||||
|
||||
# cell's name disallow '!', '.' and '@'.
|
||||
valid_cell_name_regex = '^(?![%s])[%s]*(?<![%s])$' % (
|
||||
|
||||
# This regex allows leading/trailing whitespace
|
||||
valid_name_leading_trailing_spaces_regex_base = (
|
||||
"^[%(ws)s]*[%(no_ws)s]+[%(ws)s]*$|"
|
||||
"^[%(ws)s]*[%(no_ws)s][%(no_ws)s%(ws)s]+[%(no_ws)s][%(ws)s]*$")
|
||||
|
||||
|
||||
valid_cell_name_regex = valid_name_regex_base % (
|
||||
re.escape(_printable_ws),
|
||||
re.escape(_get_printable(exclude=['!', '.', '@'])),
|
||||
re.escape(_printable_ws))
|
||||
|
||||
|
||||
# cell's name disallow '!', '.' and '@'.
|
||||
valid_cell_name_leading_trailing_spaces_regex = (
|
||||
valid_name_leading_trailing_spaces_regex_base % {
|
||||
'ws': re.escape(_printable_ws),
|
||||
'no_ws': re.escape(_get_printable_no_ws(exclude=['!', '.', '@']))})
|
||||
|
||||
|
||||
valid_name_leading_trailing_spaces_regex = (
|
||||
valid_name_leading_trailing_spaces_regex_base % {
|
||||
'ws': re.escape(_printable_ws),
|
||||
'no_ws': re.escape(_get_printable_no_ws())})
|
||||
|
||||
|
||||
boolean = {
|
||||
'type': ['boolean', 'string'],
|
||||
'enum': [True, 'True', 'TRUE', 'true', '1', 'ON', 'On', 'on',
|
||||
|
@ -128,6 +160,18 @@ cell_name = {
|
|||
}
|
||||
|
||||
|
||||
cell_name_leading_trailing_spaces = {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
'pattern': valid_cell_name_leading_trailing_spaces_regex,
|
||||
}
|
||||
|
||||
|
||||
name_with_leading_trailing_spaces = {
|
||||
'type': 'string', 'minLength': 1, 'maxLength': 255,
|
||||
'pattern': valid_name_leading_trailing_spaces_regex,
|
||||
}
|
||||
|
||||
|
||||
tcp_udp_port = {
|
||||
'type': ['integer', 'string'], 'pattern': '^[0-9]*$',
|
||||
'minimum': 0, 'maximum': 65535,
|
||||
|
|
|
@ -36,6 +36,7 @@ from nova.api.openstack.compute.legacy_v2 import ips
|
|||
from nova.api.openstack.compute.legacy_v2 import servers
|
||||
from nova.api.openstack.compute import views
|
||||
from nova.api.openstack import extensions
|
||||
from nova import availability_zones
|
||||
from nova.compute import api as compute_api
|
||||
from nova.compute import flavors
|
||||
from nova.compute import task_states
|
||||
|
@ -1398,6 +1399,14 @@ class ServersControllerUpdateTest(ControllerTest):
|
|||
self.assertEqual(FAKE_UUID, res_dict['server']['id'])
|
||||
self.assertEqual('server_test', res_dict['server']['name'])
|
||||
|
||||
def test_update_server_name_with_leading_trailing_spaces(self):
|
||||
body = {'server': {'name': ' abc def '}}
|
||||
req = self._get_request(body, {'name': 'server_test'})
|
||||
res_dict = self.controller.update(req, FAKE_UUID, body)
|
||||
|
||||
self.assertEqual(FAKE_UUID, res_dict['server']['id'])
|
||||
self.assertEqual('abc def', res_dict['server']['name'])
|
||||
|
||||
def test_update_server_name_too_long(self):
|
||||
body = {'server': {'name': 'x' * 256}}
|
||||
req = self._get_request(body, {'name': 'server_test'})
|
||||
|
@ -1644,6 +1653,19 @@ class ServersControllerRebuildInstanceTest(ControllerTest):
|
|||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, self.body)
|
||||
|
||||
def test_rebuild_instance_name_with_leading_trailing_spaces(self):
|
||||
self.body['rebuild']['name'] = ' abc def '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
|
||||
def fake_rebuild(*args, **kwargs):
|
||||
# NOTE(alex_xu): V2 API rebuild didn't strip the leading/trailing
|
||||
# spaces.
|
||||
self.assertEqual(' abc def ', kwargs['display_name'])
|
||||
|
||||
with mock.patch.object(compute_api.API, 'rebuild') as mock_rebuild:
|
||||
mock_rebuild.side_effect = fake_rebuild
|
||||
self.controller._action_rebuild(self.req, FAKE_UUID, self.body)
|
||||
|
||||
def test_rebuild_instance_name_with_spaces_in_middle(self):
|
||||
self.body['rebuild']['name'] = 'abc def'
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
|
@ -1977,6 +1999,7 @@ class ServersControllerCreateTest(test.TestCase):
|
|||
server = self.controller.create(self.req, self.body).obj['server']
|
||||
self._check_admin_pass_len(server)
|
||||
self.assertEqual(FAKE_UUID, server['id'])
|
||||
return server
|
||||
|
||||
def test_create_instance_private_flavor(self):
|
||||
values = {
|
||||
|
@ -2176,6 +2199,22 @@ class ServersControllerCreateTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self._test_create_extra, params)
|
||||
|
||||
def test_create_instance_sg_with_leading_trailing_spaces(self):
|
||||
self.flags(network_api_class='nova.network.neutronv2.api.API')
|
||||
network = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||
requested_networks = [{'uuid': network}]
|
||||
params = {'networks': requested_networks,
|
||||
'security_groups': [{'name': ' sg '}]}
|
||||
|
||||
def fake_create(*args, **kwargs):
|
||||
self.assertEqual([' sg '], kwargs['security_group'])
|
||||
return (objects.InstanceList(objects=[fakes.stub_instance_obj(
|
||||
self.req.environ['nova.context'])]), None)
|
||||
|
||||
self.stubs.Set(compute_api.API, 'create', fake_create)
|
||||
self.ext_mgr.extensions = {'os-security-groups'}
|
||||
self._test_create_extra(params)
|
||||
|
||||
def test_create_instance_with_port_with_no_fixed_ips(self):
|
||||
self.flags(network_api_class='nova.network.neutronv2.api.API')
|
||||
port_id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||
|
@ -2245,6 +2284,21 @@ class ServersControllerCreateTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
|
||||
self.req, self.body)
|
||||
|
||||
def test_create_instance_name_with_leading_trailing_spaces(self):
|
||||
self.body['server']['name'] = ' abc def '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
server = self._test_create_instance()
|
||||
self.assertEqual(
|
||||
self.body['server']['name'].strip(),
|
||||
self.instance_cache_by_uuid[server['id']]['display_name'])
|
||||
|
||||
def test_create_instance_az_with_leading_trailing_spaces(self):
|
||||
self.body['server']['availability_zone'] = ' zone1 '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
with mock.patch.object(availability_zones, 'get_availability_zones',
|
||||
return_value=[' zone1 ']):
|
||||
self._test_create_instance()
|
||||
|
||||
def test_create_instance(self):
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
res = self.controller.create(self.req, self.body).obj
|
||||
|
|
|
@ -21,6 +21,7 @@ from webob import exc
|
|||
from nova.api.openstack.compute import aggregates as aggregates_v21
|
||||
from nova.api.openstack.compute.legacy_v2.contrib import aggregates \
|
||||
as aggregates_v2
|
||||
from nova.compute import api as compute_api
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova import test
|
||||
|
@ -142,6 +143,27 @@ class AggregateTestCaseV21(test.NoDBTestCase):
|
|||
{"foo": "test",
|
||||
"availability_zone": "nova1"}})
|
||||
|
||||
def test_create_name_with_leading_trailing_spaces(self):
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
self.req, body={"aggregate":
|
||||
{"name": " test ",
|
||||
"availability_zone": "nova1"}})
|
||||
|
||||
def test_create_name_with_leading_trailing_spaces_compat_mode(self):
|
||||
|
||||
def fake_mock_aggs(context, name, az):
|
||||
self.assertEqual('test', name)
|
||||
return AGGREGATE
|
||||
|
||||
with mock.patch.object(compute_api.AggregateAPI,
|
||||
'create_aggregate') as mock_aggs:
|
||||
mock_aggs.side_effect = fake_mock_aggs
|
||||
self.req.set_legacy_v2()
|
||||
self.controller.create(self.req,
|
||||
body={"aggregate":
|
||||
{"name": " test ",
|
||||
"availability_zone": "nova1"}})
|
||||
|
||||
def test_create_with_no_availability_zone(self):
|
||||
def stub_create_aggregate(context, name, availability_zone):
|
||||
self.assertEqual(context, self.context, "context")
|
||||
|
@ -173,6 +195,28 @@ class AggregateTestCaseV21(test.NoDBTestCase):
|
|||
{"name": "test",
|
||||
"availability_zone": "x" * 256}})
|
||||
|
||||
def test_create_availability_zone_with_leading_trailing_spaces(self):
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
self.req, body={"aggregate":
|
||||
{"name": "test",
|
||||
"availability_zone": " nova1 "}})
|
||||
|
||||
def test_create_availabiltiy_zone_with_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
|
||||
def fake_mock_aggs(context, name, az):
|
||||
self.assertEqual('nova1', az)
|
||||
return AGGREGATE
|
||||
|
||||
with mock.patch.object(compute_api.AggregateAPI,
|
||||
'create_aggregate') as mock_aggs:
|
||||
mock_aggs.side_effect = fake_mock_aggs
|
||||
self.req.set_legacy_v2()
|
||||
self.controller.create(self.req,
|
||||
body={"aggregate":
|
||||
{"name": "test",
|
||||
"availability_zone": " nova1 "}})
|
||||
|
||||
def test_create_with_null_availability_zone(self):
|
||||
aggregate = {"name": "aggregate1",
|
||||
"id": "1",
|
||||
|
@ -682,3 +726,40 @@ class AggregateTestCaseV2(AggregateTestCaseV21):
|
|||
self.assertRaises(exception.AdminRequired,
|
||||
self.controller._remove_host, self.user_req,
|
||||
'1', {'host': 'fake_host'})
|
||||
|
||||
def test_create_name_with_leading_trailing_spaces(self):
|
||||
|
||||
def fake_mock_aggs(context, name, az):
|
||||
# NOTE(alex_xu): legacy v2 api didn't strip the spaces.
|
||||
self.assertEqual(' test ', name)
|
||||
return AGGREGATE
|
||||
|
||||
with mock.patch.object(compute_api.AggregateAPI,
|
||||
'create_aggregate') as mock_aggs:
|
||||
mock_aggs.side_effect = fake_mock_aggs
|
||||
self.controller.create(self.req,
|
||||
body={"aggregate":
|
||||
{"name": " test ",
|
||||
"availability_zone": "nova1"}})
|
||||
|
||||
def test_create_name_with_leading_trailing_spaces_compat_mode(self):
|
||||
pass
|
||||
|
||||
def test_create_availability_zone_with_leading_trailing_spaces(self):
|
||||
|
||||
def fake_mock_aggs(context, name, az):
|
||||
# NOTE(alex_xu): legacy v2 api didn't strip the spaces.
|
||||
self.assertEqual(' nova1 ', az)
|
||||
return AGGREGATE
|
||||
|
||||
with mock.patch.object(compute_api.AggregateAPI,
|
||||
'create_aggregate') as mock_aggs:
|
||||
mock_aggs.side_effect = fake_mock_aggs
|
||||
self.controller.create(self.req,
|
||||
body={"aggregate":
|
||||
{"name": " test ",
|
||||
"availability_zone": " nova1 "}})
|
||||
|
||||
def test_create_availabiltiy_zone_with_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
|
|
@ -313,6 +313,31 @@ class CellsTestV21(BaseCellsTest):
|
|||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_cell_create_name_with_leading_trailing_spaces(self):
|
||||
body = {'cell': {'name': ' moocow ',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
def test_cell_create_name_with_leading_trailing_spaces_compat_mode(self):
|
||||
body = {'cell': {'name': ' moocow ',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
req.set_legacy_v2()
|
||||
resp = self.controller.create(req, body=body)
|
||||
self.assertEqual('moocow', resp['cell']['name'])
|
||||
|
||||
def test_cell_create_name_with_invalid_type_raises(self):
|
||||
body = {'cell': {'name': 'moocow',
|
||||
'username': 'fred',
|
||||
|
@ -733,3 +758,19 @@ class CellsTestV2(CellsTestV21):
|
|||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.controller.update(req, 'cell1', body=body)
|
||||
|
||||
def test_cell_create_name_with_leading_trailing_spaces_compat_mode(self):
|
||||
pass
|
||||
|
||||
def test_cell_create_name_with_leading_trailing_spaces(self):
|
||||
body = {'cell': {'name': ' moocow ',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
req.environ['nova.context'] = self.context
|
||||
resp = self.controller.create(req, body=body)
|
||||
# NOTE(alex_xu): legacy v2 didn't strip the spaces.
|
||||
self.assertEqual(' moocow ', resp['cell']['name'])
|
||||
|
|
|
@ -85,6 +85,39 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
|
|||
self.controller._create_backup,
|
||||
self.req, fakes.FAKE_UUID, body=body)
|
||||
|
||||
def test_create_backup_name_with_leading_trailing_spaces(self):
|
||||
body = {
|
||||
'createBackup': {
|
||||
'name': ' test ',
|
||||
'backup_type': 'daily',
|
||||
'rotation': 1,
|
||||
},
|
||||
}
|
||||
self.assertRaises(self.validation_error,
|
||||
self.controller._create_backup,
|
||||
self.req, fakes.FAKE_UUID, body=body)
|
||||
|
||||
def test_create_backup_name_with_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
body = {
|
||||
'createBackup': {
|
||||
'name': ' test ',
|
||||
'backup_type': 'daily',
|
||||
'rotation': 1,
|
||||
},
|
||||
}
|
||||
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
|
||||
properties={})
|
||||
common.check_img_metadata_properties_quota(self.context, {})
|
||||
instance = self._stub_instance_get()
|
||||
self.compute_api.backup(self.context, instance, 'test',
|
||||
'daily', 1,
|
||||
extra_properties={}).AndReturn(image)
|
||||
self.mox.ReplayAll()
|
||||
self.req.set_legacy_v2()
|
||||
self.controller._create_backup(self.req, instance.uuid,
|
||||
body=body)
|
||||
|
||||
def test_create_backup_no_rotation(self):
|
||||
# Rotation is required for backup requests.
|
||||
body = {
|
||||
|
@ -317,6 +350,29 @@ class CreateBackupTestsV2(CreateBackupTestsV21):
|
|||
def test_create_backup_non_dict_metadata(self):
|
||||
pass
|
||||
|
||||
def test_create_backup_name_with_leading_trailing_spaces(self):
|
||||
body = {
|
||||
'createBackup': {
|
||||
'name': ' test ',
|
||||
'backup_type': 'daily',
|
||||
'rotation': 1,
|
||||
},
|
||||
}
|
||||
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
|
||||
properties={})
|
||||
common.check_img_metadata_properties_quota(self.context, {})
|
||||
instance = self._stub_instance_get()
|
||||
self.compute_api.backup(self.context, instance, ' test ',
|
||||
'daily', 1,
|
||||
extra_properties={}).AndReturn(image)
|
||||
self.mox.ReplayAll()
|
||||
self.controller._create_backup(self.req, instance.uuid,
|
||||
body=body)
|
||||
|
||||
def test_create_backup_name_with_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
||||
|
||||
class CreateBackupPolicyEnforcementv21(test.NoDBTestCase):
|
||||
|
||||
|
|
|
@ -180,8 +180,8 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
|||
def test_create_missing_disk(self):
|
||||
self._test_create_missing_parameter('disk')
|
||||
|
||||
def _create_flavor_success_case(self, body):
|
||||
req = self._get_http_request(url=self.base_url)
|
||||
def _create_flavor_success_case(self, body, req=None):
|
||||
req = req if req else self._get_http_request(url=self.base_url)
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.method = 'POST'
|
||||
req.body = jsonutils.dumps(body)
|
||||
|
@ -227,6 +227,17 @@ class FlavorManageTestV21(test.NoDBTestCase):
|
|||
self.request_body['flavor']['name'] = 'a' * 256
|
||||
self._create_flavor_bad_request_case(self.request_body)
|
||||
|
||||
def test_create_with_name_leading_trailing_spaces(self):
|
||||
self.request_body['flavor']['name'] = ' test '
|
||||
self._create_flavor_bad_request_case(self.request_body)
|
||||
|
||||
def test_create_with_name_leading_trailing_spaces_compat_mode(self):
|
||||
req = self._get_http_request(url=self.base_url)
|
||||
req.set_legacy_v2()
|
||||
self.request_body['flavor']['name'] = ' test '
|
||||
body = self._create_flavor_success_case(self.request_body, req)
|
||||
self.assertEqual('test', body['flavor']['name'])
|
||||
|
||||
def test_create_without_flavorname(self):
|
||||
del self.request_body['flavor']['name']
|
||||
self._create_flavor_bad_request_case(self.request_body)
|
||||
|
@ -444,6 +455,15 @@ class FlavorManageTestV2(FlavorManageTestV21):
|
|||
def _get_http_request(self, url=''):
|
||||
return fakes.HTTPRequest.blank(url, use_admin_context=False)
|
||||
|
||||
def test_create_with_name_leading_trailing_spaces(self):
|
||||
req = self._get_http_request(url=self.base_url)
|
||||
self.request_body['flavor']['name'] = ' test '
|
||||
body = self._create_flavor_success_case(self.request_body, req)
|
||||
self.assertEqual('test', body['flavor']['name'])
|
||||
|
||||
def test_create_with_name_leading_trailing_spaces_compat_mode(self):
|
||||
pass
|
||||
|
||||
|
||||
class PrivateFlavorManageTestV2(PrivateFlavorManageTestV21):
|
||||
controller = flavormanage_v2.FlavorManageController()
|
||||
|
|
|
@ -21,6 +21,7 @@ from nova.api.openstack.compute import keypairs as keypairs_v21
|
|||
from nova.api.openstack.compute.legacy_v2.contrib import keypairs \
|
||||
as keypairs_v2
|
||||
from nova.api.openstack import wsgi as os_wsgi
|
||||
from nova.compute import api as compute_api
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import objects
|
||||
|
@ -40,6 +41,8 @@ keypair_data = {
|
|||
'fingerprint': 'FAKE_FINGERPRINT',
|
||||
}
|
||||
|
||||
FAKE_UUID = 'b48316c5-71e8-45e4-9884-6c78055b9b13'
|
||||
|
||||
|
||||
def fake_keypair(name):
|
||||
return dict(test_keypair.fake_keypair,
|
||||
|
@ -124,6 +127,22 @@ class KeypairsTestV21(test.TestCase):
|
|||
self._test_keypair_create_bad_request_case(body,
|
||||
self.validation_error)
|
||||
|
||||
def test_keypair_create_with_name_leading_trailing_spaces(self):
|
||||
body = {
|
||||
'keypair': {
|
||||
'name': ' test '
|
||||
}
|
||||
}
|
||||
self._test_keypair_create_bad_request_case(body,
|
||||
self.validation_error)
|
||||
|
||||
def test_keypair_create_with_name_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
body = {'keypair': {'name': ' test '}}
|
||||
self.req.set_legacy_v2()
|
||||
res_dict = self.controller.create(self.req, body=body)
|
||||
self.assertEqual('test', res_dict['keypair']['name'])
|
||||
|
||||
def test_keypair_create_with_non_alphanumeric_name(self):
|
||||
body = {
|
||||
'keypair': {
|
||||
|
@ -295,6 +314,36 @@ class KeypairsTestV21(test.TestCase):
|
|||
def _assert_keypair_type(self, res_dict):
|
||||
self.assertNotIn('type', res_dict['keypair'])
|
||||
|
||||
def test_create_server_keypair_name_with_leading_trailing(self):
|
||||
req = fakes.HTTPRequest.blank(self.base_url + '/servers')
|
||||
req.method = 'POST'
|
||||
req.headers["content-type"] = "application/json"
|
||||
req.body = jsonutils.dumps({'server': {'name': 'test',
|
||||
'flavorRef': 1,
|
||||
'keypair_name': ' abc ',
|
||||
'imageRef': FAKE_UUID}})
|
||||
res = req.get_response(self.app_server)
|
||||
self.assertEqual(400, res.status_code)
|
||||
self.assertIn('keypair_name', res.body)
|
||||
|
||||
@mock.patch.object(compute_api.API, 'create')
|
||||
def test_create_server_keypair_name_with_leading_trailing_compat_mode(
|
||||
self, mock_create):
|
||||
mock_create.return_value = (
|
||||
objects.InstanceList(objects=[
|
||||
fakes.stub_instance_obj(ctxt=None, id=1)]),
|
||||
None)
|
||||
req = fakes.HTTPRequest.blank(self.base_url + '/servers')
|
||||
req.method = 'POST'
|
||||
req.headers["content-type"] = "application/json"
|
||||
req.body = jsonutils.dumps({'server': {'name': 'test',
|
||||
'flavorRef': 1,
|
||||
'keypair_name': ' abc ',
|
||||
'imageRef': FAKE_UUID}})
|
||||
req.set_legacy_v2()
|
||||
res = req.get_response(self.app_server)
|
||||
self.assertEqual(202, res.status_code)
|
||||
|
||||
|
||||
class KeypairPolicyTestV21(test.TestCase):
|
||||
KeyPairController = keypairs_v21.KeypairController()
|
||||
|
@ -388,6 +437,37 @@ class KeypairsTestV2(KeypairsTestV21):
|
|||
self.app_server = fakes.wsgi_app(init_only=('servers',))
|
||||
self.controller = keypairs_v2.KeypairController()
|
||||
|
||||
def test_keypair_create_with_name_leading_trailing_spaces(
|
||||
self):
|
||||
body = {'keypair': {'name': ' test '}}
|
||||
self.req.set_legacy_v2()
|
||||
res_dict = self.controller.create(self.req, body=body)
|
||||
self.assertEqual(' test ', res_dict['keypair']['name'])
|
||||
|
||||
def test_keypair_create_with_name_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
||||
def test_create_server_keypair_name_with_leading_trailing(self):
|
||||
pass
|
||||
|
||||
@mock.patch.object(compute_api.API, 'create')
|
||||
def test_create_server_keypair_name_with_leading_trailing_compat_mode(
|
||||
self, mock_create):
|
||||
mock_create.return_value = (
|
||||
objects.InstanceList(objects=[
|
||||
fakes.stub_instance_obj(ctxt=None, id=1)]),
|
||||
None)
|
||||
req = fakes.HTTPRequest.blank(self.base_url + '/servers')
|
||||
req.method = 'POST'
|
||||
req.headers["content-type"] = "application/json"
|
||||
req.body = jsonutils.dumps({'server': {'name': 'test',
|
||||
'flavorRef': 1,
|
||||
'keypair_name': ' abc ',
|
||||
'imageRef': FAKE_UUID}})
|
||||
res = req.get_response(self.app_server)
|
||||
self.assertEqual(202, res.status_code)
|
||||
|
||||
|
||||
class KeypairsTestV22(KeypairsTestV21):
|
||||
wsgi_api_version = '2.2'
|
||||
|
@ -401,10 +481,26 @@ class KeypairsTestV22(KeypairsTestV21):
|
|||
def _assert_keypair_type(self, res_dict):
|
||||
self.assertEqual('ssh', res_dict['keypair']['type'])
|
||||
|
||||
def test_keypair_create_with_name_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
||||
def test_create_server_keypair_name_with_leading_trailing_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
||||
|
||||
class KeypairsTestV210(KeypairsTestV22):
|
||||
wsgi_api_version = '2.10'
|
||||
|
||||
def test_keypair_create_with_name_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
||||
def test_create_server_keypair_name_with_leading_trailing_compat_mode(
|
||||
self):
|
||||
pass
|
||||
|
||||
def test_keypair_list_other_user(self):
|
||||
req = fakes.HTTPRequest.blank(self.base_url +
|
||||
'/os-keypairs?user_id=foo',
|
||||
|
|
|
@ -40,13 +40,12 @@ from nova.api.openstack.compute import disk_config
|
|||
from nova.api.openstack.compute import extension_info
|
||||
from nova.api.openstack.compute import ips
|
||||
from nova.api.openstack.compute import keypairs
|
||||
from nova.api.openstack.compute.schemas import disk_config \
|
||||
as disk_config_schema
|
||||
from nova.api.openstack.compute.schemas import servers as servers_schema
|
||||
from nova.api.openstack.compute import servers
|
||||
from nova.api.openstack.compute import views
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi as os_wsgi
|
||||
from nova import availability_zones
|
||||
from nova.compute import api as compute_api
|
||||
from nova.compute import flavors
|
||||
from nova.compute import task_states
|
||||
|
@ -1538,6 +1537,27 @@ class ServersControllerRebuildInstanceTest(ControllerTest):
|
|||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.controller._action_rebuild(self.req, FAKE_UUID, body=self.body)
|
||||
|
||||
def test_rebuild_instance_name_with_leading_trailing_spaces(self):
|
||||
self.body['rebuild']['name'] = ' abc def '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
|
||||
def test_rebuild_instance_name_with_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
self.body['rebuild']['name'] = ' abc def '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.req.set_legacy_v2()
|
||||
|
||||
def fake_rebuild(*args, **kwargs):
|
||||
self.assertEqual('abc def', kwargs['display_name'])
|
||||
|
||||
with mock.patch.object(compute_api.API, 'rebuild') as mock_rebuild:
|
||||
mock_rebuild.side_effect = fake_rebuild
|
||||
self.controller._action_rebuild(self.req, FAKE_UUID,
|
||||
body=self.body)
|
||||
|
||||
def test_rebuild_instance_with_blank_metadata_key(self):
|
||||
self.body['rebuild']['metadata'][''] = 'world'
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
|
@ -1850,6 +1870,28 @@ class ServersControllerUpdateTest(ControllerTest):
|
|||
req.body = jsonutils.dumps(body)
|
||||
self.controller.update(req, FAKE_UUID, body=body)
|
||||
|
||||
def test_update_server_name_with_leading_trailing_spaces(self):
|
||||
self.stubs.Set(db, 'instance_get',
|
||||
fakes.fake_instance_get(name='server_test'))
|
||||
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
||||
req.method = 'PUT'
|
||||
req.content_type = 'application/json'
|
||||
body = {'server': {'name': ' abc def '}}
|
||||
req.body = jsonutils.dumps(body)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update, req, FAKE_UUID, body=body)
|
||||
|
||||
def test_update_server_name_with_leading_trailing_spaces_compat_mode(self):
|
||||
self.stubs.Set(db, 'instance_get',
|
||||
fakes.fake_instance_get(name='server_test'))
|
||||
req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID)
|
||||
req.method = 'PUT'
|
||||
req.content_type = 'application/json'
|
||||
body = {'server': {'name': ' abc def '}}
|
||||
req.body = jsonutils.dumps(body)
|
||||
req.set_legacy_v2()
|
||||
self.controller.update(req, FAKE_UUID, body=body)
|
||||
|
||||
def test_update_server_admin_password_extra_arg(self):
|
||||
inst_dict = dict(name='server_test', admin_password='bacon')
|
||||
body = dict(server=inst_dict)
|
||||
|
@ -2339,6 +2381,31 @@ class ServersControllerCreateTest(test.TestCase):
|
|||
self.assertRaises(webob.exc.HTTPConflict,
|
||||
self._test_create_extra, params)
|
||||
|
||||
def test_create_instance_secgroup_leading_trailing_spaces(self):
|
||||
network = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||
requested_networks = [{'uuid': network}]
|
||||
params = {'networks': requested_networks,
|
||||
'security_groups': [{'name': ' sg '}]}
|
||||
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self._test_create_extra, params)
|
||||
|
||||
def test_create_instance_secgroup_leading_trailing_spaces_compat_mode(
|
||||
self):
|
||||
network = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||
requested_networks = [{'uuid': network}]
|
||||
params = {'networks': requested_networks,
|
||||
'security_groups': [{'name': ' sg '}]}
|
||||
|
||||
def fake_create(*args, **kwargs):
|
||||
self.assertEqual([' sg '], kwargs['security_group'])
|
||||
return (objects.InstanceList(objects=[fakes.stub_instance_obj(
|
||||
self.req.environ['nova.context'])]), None)
|
||||
|
||||
self.stubs.Set(compute_api.API, 'create', fake_create)
|
||||
self.req.set_legacy_v2()
|
||||
self._test_create_extra(params)
|
||||
|
||||
def test_create_instance_with_networks_disabled_neutronv2(self):
|
||||
self.flags(network_api_class='nova.network.neutronv2.api.API')
|
||||
net_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
||||
|
@ -2400,6 +2467,25 @@ class ServersControllerCreateTest(test.TestCase):
|
|||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.controller.create(self.req, body=self.body)
|
||||
|
||||
def test_create_instance_name_with_leading_trailing_spaces(self):
|
||||
# proper local hrefs must start with 'http://localhost/v2/'
|
||||
image_href = 'http://localhost/v2/images/%s' % self.image_uuid
|
||||
self.body['server']['name'] = ' abc def '
|
||||
self.body['server']['imageRef'] = image_href
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, self.req, body=self.body)
|
||||
|
||||
def test_create_instance_name_with_leading_trailing_spaces_in_compat_mode(
|
||||
self):
|
||||
# proper local hrefs must start with 'http://localhost/v2/'
|
||||
image_href = 'http://localhost/v2/images/%s' % self.image_uuid
|
||||
self.body['server']['name'] = ' abc def '
|
||||
self.body['server']['imageRef'] = image_href
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.req.set_legacy_v2()
|
||||
self.controller.create(self.req, body=self.body)
|
||||
|
||||
def test_create_instance_name_all_blank_spaces(self):
|
||||
# proper local hrefs must start with 'http://localhost/v2/'
|
||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
||||
|
@ -2424,6 +2510,28 @@ class ServersControllerCreateTest(test.TestCase):
|
|||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, req, body=body)
|
||||
|
||||
def test_create_az_with_leading_trailing_spaces(self):
|
||||
# proper local hrefs must start with 'http://localhost/v2/'
|
||||
image_href = 'http://localhost/v2/images/%s' % self.image_uuid
|
||||
self.body['server']['imageRef'] = image_href
|
||||
self.body['server']['availability_zone'] = ' zone1 '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create, self.req, body=self.body)
|
||||
|
||||
def test_create_az_with_leading_trailing_spaces_in_compat_mode(
|
||||
self):
|
||||
# proper local hrefs must start with 'http://localhost/v2/'
|
||||
image_href = 'http://localhost/v2/images/%s' % self.image_uuid
|
||||
self.body['server']['name'] = ' abc def '
|
||||
self.body['server']['imageRef'] = image_href
|
||||
self.body['server']['availability_zones'] = ' zone1 '
|
||||
self.req.body = jsonutils.dumps(self.body)
|
||||
self.req.set_legacy_v2()
|
||||
with mock.patch.object(availability_zones, 'get_availability_zones',
|
||||
return_value=[' zone1 ']):
|
||||
self.controller.create(self.req, body=self.body)
|
||||
|
||||
def test_create_instance(self):
|
||||
# proper local hrefs must start with 'http://localhost/v2/'
|
||||
image_href = 'http://localhost/v2/images/%s' % self.image_uuid
|
||||
|
@ -3561,10 +3669,21 @@ class FakeExt(extensions.V21APIExtensionBase):
|
|||
name = "DiskConfig"
|
||||
alias = 'os-disk-config'
|
||||
version = 1
|
||||
fake_schema = {'fake_ext_attr': {'type': 'string'}}
|
||||
|
||||
def fake_extension_point(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def fake_schema_extension_point(self, version):
|
||||
if version == '2.1':
|
||||
return self.fake_schema
|
||||
elif version == '2.0':
|
||||
return {}
|
||||
# This fake method should reuturn the schema for expected version
|
||||
# Return None will make the tests failed, that means there is something
|
||||
# in the code.
|
||||
return None
|
||||
|
||||
def get_controller_extensions(self):
|
||||
return []
|
||||
|
||||
|
@ -3606,11 +3725,13 @@ class TestServersExtensionPoint(test.NoDBTestCase):
|
|||
class TestServersExtensionSchema(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
super(TestServersExtensionSchema, self).setUp()
|
||||
CONF.set_override('extensions_whitelist', ['disk_config'], 'osapi_v21')
|
||||
CONF.set_override('extensions_whitelist', ['os-disk-config'],
|
||||
'osapi_v21')
|
||||
self.stubs.Set(disk_config, 'DiskConfig', FakeExt)
|
||||
|
||||
def _test_load_extension_schema(self, name):
|
||||
setattr(FakeExt, 'get_server_%s_schema' % name,
|
||||
FakeExt.fake_extension_point)
|
||||
FakeExt.fake_schema_extension_point)
|
||||
ext_info = extension_info.LoadedExtensionInfo()
|
||||
controller = servers.ServersController(extension_info=ext_info)
|
||||
self.assertTrue(hasattr(controller, '%s_schema_manager' % name))
|
||||
|
@ -3623,7 +3744,7 @@ class TestServersExtensionSchema(test.NoDBTestCase):
|
|||
# because of the above extensions_whitelist.
|
||||
expected_schema = copy.deepcopy(servers_schema.base_create)
|
||||
expected_schema['properties']['server']['properties'].update(
|
||||
disk_config_schema.server_create)
|
||||
FakeExt.fake_schema)
|
||||
|
||||
actual_schema = self._test_load_extension_schema('create')
|
||||
self.assertEqual(expected_schema, actual_schema)
|
||||
|
@ -3633,7 +3754,7 @@ class TestServersExtensionSchema(test.NoDBTestCase):
|
|||
# here checks that any extension is not added to the schema.
|
||||
expected_schema = copy.deepcopy(servers_schema.base_update)
|
||||
expected_schema['properties']['server']['properties'].update(
|
||||
disk_config_schema.server_create)
|
||||
FakeExt.fake_schema)
|
||||
|
||||
actual_schema = self._test_load_extension_schema('update')
|
||||
self.assertEqual(expected_schema, actual_schema)
|
||||
|
@ -3643,7 +3764,7 @@ class TestServersExtensionSchema(test.NoDBTestCase):
|
|||
# here checks that any extension is not added to the schema.
|
||||
expected_schema = copy.deepcopy(servers_schema.base_rebuild)
|
||||
expected_schema['properties']['rebuild']['properties'].update(
|
||||
disk_config_schema.server_create)
|
||||
FakeExt.fake_schema)
|
||||
|
||||
actual_schema = self._test_load_extension_schema('rebuild')
|
||||
self.assertEqual(expected_schema, actual_schema)
|
||||
|
@ -3653,7 +3774,7 @@ class TestServersExtensionSchema(test.NoDBTestCase):
|
|||
# here checks that any extension is not added to the schema.
|
||||
expected_schema = copy.deepcopy(servers_schema.base_resize)
|
||||
expected_schema['properties']['resize']['properties'].update(
|
||||
disk_config_schema.server_create)
|
||||
FakeExt.fake_schema)
|
||||
|
||||
actual_schema = self._test_load_extension_schema('resize')
|
||||
self.assertEqual(expected_schema, actual_schema)
|
||||
|
|
|
@ -671,6 +671,84 @@ class NameTestCase(APIValidationTestCase):
|
|||
pass
|
||||
|
||||
|
||||
class NameWithLeadingTrailingSpacesTestCase(APIValidationTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NameWithLeadingTrailingSpacesTestCase, self).setUp()
|
||||
schema = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'foo': parameter_types.name_with_leading_trailing_spaces,
|
||||
},
|
||||
}
|
||||
|
||||
@validation.schema(request_body_schema=schema)
|
||||
def post(req, body):
|
||||
return 'Validation succeeded.'
|
||||
|
||||
self.post = post
|
||||
|
||||
def test_validate_name(self):
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'm1.small'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'my server'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'a'}, req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': u'\u0434'}, req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': u'\u0434\u2006\ufffd'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': ' abc '},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': 'abc abc abc'},
|
||||
req=FakeRequest()))
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': ' abc abc abc '},
|
||||
req=FakeRequest()))
|
||||
# leading unicode space
|
||||
self.assertEqual('Validation succeeded.',
|
||||
self.post(body={'foo': '\xa0abc'},
|
||||
req=FakeRequest()))
|
||||
|
||||
def test_validate_name_fails(self):
|
||||
detail = (u"Invalid input for field/attribute foo. Value: ."
|
||||
u" ' ' does not match .*")
|
||||
self.check_validation_error(self.post, body={'foo': ' '},
|
||||
expected_detail=detail)
|
||||
|
||||
# NOTE(stpierre): Quoting for the unicode values in the error
|
||||
# messages below gets *really* messy, so we just wildcard it
|
||||
# out. (e.g., '.* does not match'). In practice, we don't
|
||||
# particularly care about that part of the error message.
|
||||
|
||||
# unicode space
|
||||
detail = (u"Invalid input for field/attribute foo. Value: \xa0."
|
||||
u' .* does not match .*')
|
||||
self.check_validation_error(self.post, body={'foo': u'\xa0'},
|
||||
expected_detail=detail)
|
||||
|
||||
# non-printable unicode
|
||||
detail = (u"Invalid input for field/attribute foo. Value: \uffff."
|
||||
u" .* does not match .*")
|
||||
self.check_validation_error(self.post, body={'foo': u'\uffff'},
|
||||
expected_detail=detail)
|
||||
|
||||
# four-byte unicode, if supported by this python build
|
||||
try:
|
||||
detail = (u"Invalid input for field/attribute foo. Value: "
|
||||
u"\U00010000. .* does not match .*")
|
||||
self.check_validation_error(self.post, body={'foo': u'\U00010000'},
|
||||
expected_detail=detail)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
class NoneTypeTestCase(APIValidationTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
Loading…
Reference in New Issue