Merge "Filter leading/trailing spaces for name field in v2.1 compat mode"

This commit is contained in:
Jenkins 2015-09-23 12:31:00 +00:00 committed by Gerrit Code Review
commit 10ac88f2b4
36 changed files with 810 additions and 59 deletions

View File

@ -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.")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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']

View File

@ -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

View File

@ -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"])

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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': {

View File

@ -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,
}

View File

@ -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': {

View File

@ -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)

View File

@ -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)

View File

@ -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,
}

View File

@ -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)

View File

@ -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': {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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'])

View File

@ -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):

View File

@ -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()

View File

@ -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',

View File

@ -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)

View File

@ -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):