Deprecate file injection

This microversion makes the following changes:

1. Deprecates personality files from POST /servers and the rebuild
   server action APIs.
2. Adds the ability to pass new user_data to the rebuild server
   action API.
3. Personality / file injection related limits and quota resources
   are removed from the limits, os-quota-sets and os-quota-class-sets
   APIs.

Implements blueprint deprecate-file-injection

Change-Id: Ia89eeb6725459c35369e8f790f68ad9180bd3aba
This commit is contained in:
Matt Riedemann
2017-11-21 15:02:45 -05:00
parent 3cec0cb584
commit 126c3d4c78
59 changed files with 1026 additions and 62 deletions
+28
View File
@@ -1739,6 +1739,7 @@ contents:
in: body
required: true
type: string
max_version: 2.56
cores: &cores
description: |
The number of allowed server cores for each tenant.
@@ -3298,6 +3299,7 @@ injected_file_content_bytes:
in: body
required: true
type: integer
max_version: 2.56
injected_file_content_bytes_quota_details:
description: |
The object of detailed injected file content bytes quota,
@@ -3306,18 +3308,21 @@ injected_file_content_bytes_quota_details:
in: body
required: true
type: object
max_version: 2.56
injected_file_content_bytes_quota_optional:
description: |
The number of allowed bytes of content for each injected file.
in: body
required: false
type: integer
max_version: 2.56
injected_file_path_bytes:
description: |
The number of allowed bytes for each injected file path.
in: body
required: true
type: integer
max_version: 2.56
injected_file_path_bytes_quota_details:
description: |
The object of detailed injected file path bytes quota,
@@ -3326,18 +3331,21 @@ injected_file_path_bytes_quota_details:
in: body
required: true
type: object
max_version: 2.56
injected_file_path_bytes_quota_optional:
description: |
The number of allowed bytes for each injected file path.
in: body
required: false
type: integer
max_version: 2.56
injected_files: &injected_files
description: |
The number of allowed injected files for each tenant.
in: body
required: true
type: integer
max_version: 2.56
injected_files_quota_class: &injected_files_quota_class
<<: *injected_files
description: |
@@ -3352,12 +3360,14 @@ injected_files_quota_details:
in: body
required: true
type: object
max_version: 2.56
injected_files_quota_optional:
description: |
The number of allowed injected files for each tenant.
in: body
required: false
type: integer
max_version: 2.56
injectNetworkInfo:
description: |
The action.
@@ -4573,6 +4583,7 @@ path:
in: body
required: true
type: string
max_version: 2.56
pause:
description: |
The action to pause a server.
@@ -4601,6 +4612,7 @@ personality:
in: body
required: false
type: array
max_version: 2.56
policies:
description: |
A list of exactly one policy name to associate with the server group. The
@@ -5844,6 +5856,22 @@ user_data:
in: body
required: false
type: string
user_data_rebuild_req:
description: |
Configuration information or scripts to use upon rebuild.
Must be Base64 encoded. If ``null`` is specified, the existing user_data
is unset.
in: body
required: false
type: string
min_version: 2.57
user_data_rebuild_resp:
in: body
required: true
type: string
description: |
The current user_data for the instance.
min_version: 2.57
user_id:
description: |
The user ID of the user who owns the server.
+2
View File
@@ -488,6 +488,7 @@ Request
- preserve_ephemeral: preserve_ephemeral
- description: server_description
- key_name: key_name_rebuild_req
- user_data: user_data_rebuild_req
**Example Rebuild Server (rebuild Action) (v2.54)**
@@ -536,6 +537,7 @@ Response
- description: server_description_resp
- tags: tags
- key_name: key_name_rebuild_resp
- user_data: user_data_rebuild_resp
**Example Rebuild Server (rebuild Action) (v2.54)**
+4
View File
@@ -44,6 +44,10 @@ the fixed IP address to assign to the server interface.
**Server personality**
.. note:: The use of personality files is deprecated starting with the 2.57
microversion. Use ``metadata`` and ``user_data`` to customize a server
instance.
To customize the personality of a server instance, you can inject data
into its file system. For example, you might insert ssh keys, set
configuration files, or store data that you want to retrieve from inside
@@ -0,0 +1,18 @@
{
"limits": {
"absolute": {
"maxServerMeta": 128,
"maxTotalCores": 20,
"maxTotalInstances": 10,
"maxTotalKeypairs": 100,
"maxTotalRAMSize": 51200,
"maxServerGroups": 10,
"maxServerGroupMembers": 10,
"totalCoresUsed": 0,
"totalInstancesUsed": 0,
"totalRAMUsed": 0,
"totalServerGroupsUsed": 0
},
"rate": []
}
}
@@ -0,0 +1,12 @@
{
"quota_class_set": {
"cores": 20,
"id": "test_class",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,11 @@
{
"quota_class_set": {
"instances": 50,
"cores": 50,
"ram": 51200,
"metadata_items": 128,
"key_pairs": 100,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,11 @@
{
"quota_class_set": {
"cores": 50,
"instances": 50,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,12 @@
{
"quota_set": {
"cores": 20,
"id": "fake_tenant",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,40 @@
{
"quota_set": {
"cores": {
"in_use": 0,
"limit": 20,
"reserved": 0
},
"id": "fake_tenant",
"instances": {
"in_use": 0,
"limit": 10,
"reserved": 0
},
"key_pairs": {
"in_use": 0,
"limit": 100,
"reserved": 0
},
"metadata_items": {
"in_use": 0,
"limit": 128,
"reserved": 0
},
"ram": {
"in_use": 0,
"limit": 51200,
"reserved": 0
},
"server_group_members": {
"in_use": 0,
"limit": 10,
"reserved": 0
},
"server_groups": {
"in_use": 0,
"limit": 10,
"reserved": 0
}
}
}
@@ -0,0 +1,12 @@
{
"quota_set": {
"cores": 20,
"id": "fake_tenant",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,6 @@
{
"quota_set": {
"force": "True",
"instances": 45
}
}
@@ -0,0 +1,11 @@
{
"quota_set": {
"cores": 20,
"instances": 45,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,5 @@
{
"quota_set": {
"instances": 20
}
}
@@ -0,0 +1,11 @@
{
"quota_set": {
"cores": 20,
"instances": 20,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,12 @@
{
"quota_set": {
"cores": 20,
"id": "fake_tenant",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,5 @@
{
"quota_set": {
"instances": 9
}
}
@@ -0,0 +1,11 @@
{
"quota_set": {
"cores": 20,
"instances": 9,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,61 @@
{
"server": {
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"addresses": {
"private": [
{
"addr": "192.168.0.3",
"version": 4
}
]
},
"adminPass": "seekr3t",
"created": "2013-11-14T06:29:00Z",
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "28d8d56f0e3a77e20891f455721cbb68032e017045e20aa5dfc6cb66",
"id": "a0a80a94-3d81-4a10-822a-daa0cf9e870b",
"image": {
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
"links": [
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
"rel": "bookmark"
}
]
},
"links": [
{
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/servers/a0a80a94-3d81-4a10-822a-daa0cf9e870b",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/a0a80a94-3d81-4a10-822a-daa0cf9e870b",
"rel": "bookmark"
}
],
"locked": false,
"metadata": {
"meta_var": "meta_val"
},
"name": "foobar",
"key_name": "new-key",
"description": "description of foobar",
"progress": 0,
"status": "ACTIVE",
"OS-DCF:diskConfig": "AUTO",
"tenant_id": "6f70656e737461636b20342065766572",
"updated": "2013-11-14T06:29:02Z",
"user_id": "fake",
"tags": [],
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}
@@ -0,0 +1,15 @@
{
"rebuild" : {
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"imageRef": "70a599e0-31e7-49b7-b260-868f441e862b",
"name": "foobar",
"key_name": "new-key",
"description": "description of foobar",
"adminPass": "seekr3t",
"metadata" : {
"meta_var": "meta_val"
},
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}
@@ -0,0 +1,21 @@
{
"server" : {
"accessIPv4": "1.2.3.4",
"accessIPv6": "80fe::",
"name" : "new-server-test",
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
"flavorRef" : "http://openstack.example.com/flavors/1",
"availability_zone": "nova",
"OS-DCF:diskConfig": "AUTO",
"metadata" : {
"My Server Name" : "Apache1"
},
"security_groups": [
{
"name": "default"
}
],
"user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
"networks": "auto"
}
}
@@ -0,0 +1,22 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"adminPass": "S5wqy9sPYUvU",
"id": "97108291-2fd7-4dc2-a909-eaae0306a6a9",
"links": [
{
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/97108291-2fd7-4dc2-a909-eaae0306a6a9",
"rel": "self"
},
{
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/97108291-2fd7-4dc2-a909-eaae0306a6a9",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}
@@ -19,7 +19,7 @@
}
],
"status": "CURRENT",
"version": "2.56",
"version": "2.57",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}
@@ -22,7 +22,7 @@
}
],
"status": "CURRENT",
"version": "2.56",
"version": "2.57",
"min_version": "2.1",
"updated": "2013-07-23T11:33:21Z"
}
+5 -1
View File
@@ -133,6 +133,10 @@ REST_API_VERSION_HISTORY = """REST API Version History:
* 2.56 - Add a host parameter in migrate request body in order to
enable users to specify a target host in cold migration.
The target host is checked by the scheduler.
* 2.57 - Deprecated personality files from POST /servers and the rebuild
server action APIs. Added the ability to pass new user_data to
the rebuild server action API. Personality / file injection
related limits and quota resources are also removed.
"""
# The minimum and maximum versions of the API supported
@@ -141,7 +145,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
# Note(cyeoh): This only applies for the v2.1 API once microversions
# support is fully merged. It does not affect the V2 API.
_MIN_API_VERSION = "2.1"
_MAX_API_VERSION = "2.56"
_MAX_API_VERSION = "2.57"
DEFAULT_API_VERSION = _MIN_API_VERSION
# Almost all proxy APIs which are related to network, images and baremetal
+20 -5
View File
@@ -32,6 +32,15 @@ from nova import quota
QUOTAS = quota.QUOTAS
# This is a list of limits which needs to filter out from the API response.
# This is due to the deprecation of network related proxy APIs, the related
# limit should be removed from the API also.
FILTERED_LIMITS_2_36 = ['floating_ips', 'security_groups',
'security_group_rules']
FILTERED_LIMITS_2_57 = list(FILTERED_LIMITS_2_36)
FILTERED_LIMITS_2_57.extend(['injected_files', 'injected_file_content_bytes'])
class LimitsController(wsgi.Controller):
"""Controller for accessing limits in the OpenStack API."""
@@ -47,16 +56,22 @@ class LimitsController(wsgi.Controller):
@extensions.expected_errors(())
@validation.query_schema(limits.limits_query_schema)
def index(self, req):
return self._index(req, filter_result=True)
return self._index(req, FILTERED_LIMITS_2_36)
@wsgi.Controller.api_version( # noqa
MIN_WITHOUT_IMAGE_META_PROXY_API_VERSION) # noqa
MIN_WITHOUT_IMAGE_META_PROXY_API_VERSION, '2.56') # noqa
@extensions.expected_errors(())
@validation.query_schema(limits.limits_query_schema)
def index(self, req):
return self._index(req, filter_result=True, max_image_meta=False)
return self._index(req, FILTERED_LIMITS_2_36, max_image_meta=False)
def _index(self, req, filter_result=False, max_image_meta=True):
@wsgi.Controller.api_version('2.57') # noqa
@extensions.expected_errors(())
@validation.query_schema(limits.limits_query_schema)
def index(self, req):
return self._index(req, FILTERED_LIMITS_2_57, max_image_meta=False)
def _index(self, req, filtered_limits=None, max_image_meta=True):
"""Return all global limit information."""
context = req.environ['nova.context']
context.can(limits_policies.BASE_POLICY_NAME)
@@ -66,5 +81,5 @@ class LimitsController(wsgi.Controller):
abs_limits = {k: v['limit'] for k, v in quotas.items()}
builder = limits_views.ViewBuilder()
return builder.build(abs_limits, filter_result=filter_result,
return builder.build(abs_limits, filtered_limits=filtered_limits,
max_image_meta=max_image_meta)
+49 -11
View File
@@ -16,7 +16,6 @@
import copy
import webob
from nova.api.openstack import api_version_request
from nova.api.openstack.compute.schemas import quota_classes
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
@@ -36,8 +35,13 @@ EXTENDED_QUOTAS = ['server_groups', 'server_group_members']
# NOTE(gmann): Network related quotas are filter out in
# microversion 2.50. Bug#1701211.
FILTERED_QUOTAS = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
FILTERED_QUOTAS_2_50 = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
# Microversion 2.57 removes personality (injected) files from the API.
FILTERED_QUOTAS_2_57 = list(FILTERED_QUOTAS_2_50)
FILTERED_QUOTAS_2_57.extend(['injected_files', 'injected_file_content_bytes',
'injected_file_path_bytes'])
class QuotaClassSetsController(wsgi.Controller):
@@ -47,7 +51,8 @@ class QuotaClassSetsController(wsgi.Controller):
def __init__(self, **kwargs):
self.supported_quotas = QUOTAS.resources
def _format_quota_set(self, quota_class, quota_set, req):
def _format_quota_set(self, quota_class, quota_set, filtered_quotas=None,
exclude_server_groups=False):
"""Convert the quota object to a result dict."""
if quota_class:
@@ -55,13 +60,13 @@ class QuotaClassSetsController(wsgi.Controller):
else:
result = {}
original_quotas = copy.deepcopy(self.supported_quotas)
if api_version_request.is_supported(req, min_version="2.50"):
if filtered_quotas:
original_quotas = [resource for resource in original_quotas
if resource not in FILTERED_QUOTAS]
if resource not in filtered_quotas]
# NOTE(gmann): Before microversion v2.50, v2.1 API does not return the
# 'server_groups' & 'server_group_members' key in quota class API
# response.
else:
if exclude_server_groups:
for resource in EXTENDED_QUOTAS:
original_quotas.remove(resource)
for resource in original_quotas:
@@ -70,17 +75,49 @@ class QuotaClassSetsController(wsgi.Controller):
return dict(quota_class_set=result)
@wsgi.Controller.api_version('2.1', '2.49')
@extensions.expected_errors(())
def show(self, req, id):
return self._show(req, id, exclude_server_groups=True)
@wsgi.Controller.api_version('2.50', '2.56') # noqa
@extensions.expected_errors(())
def show(self, req, id):
return self._show(req, id, FILTERED_QUOTAS_2_50)
@wsgi.Controller.api_version('2.57') # noqa
@extensions.expected_errors(())
def show(self, req, id):
return self._show(req, id, FILTERED_QUOTAS_2_57)
def _show(self, req, id, filtered_quotas=None,
exclude_server_groups=False):
context = req.environ['nova.context']
context.can(qcs_policies.POLICY_ROOT % 'show', {'quota_class': id})
values = QUOTAS.get_class_quotas(context, id)
return self._format_quota_set(id, values, req)
return self._format_quota_set(id, values, filtered_quotas,
exclude_server_groups)
@wsgi.Controller.api_version("2.1", "2.49") # noqa
@extensions.expected_errors(400)
@validation.schema(quota_classes.update, "2.0", "2.49")
@validation.schema(quota_classes.update_v250, "2.50")
@validation.schema(quota_classes.update)
def update(self, req, id, body):
return self._update(req, id, body, exclude_server_groups=True)
@wsgi.Controller.api_version("2.50", "2.56") # noqa
@extensions.expected_errors(400)
@validation.schema(quota_classes.update_v250)
def update(self, req, id, body):
return self._update(req, id, body, FILTERED_QUOTAS_2_50)
@wsgi.Controller.api_version("2.57") # noqa
@extensions.expected_errors(400)
@validation.schema(quota_classes.update_v257)
def update(self, req, id, body):
return self._update(req, id, body, FILTERED_QUOTAS_2_57)
def _update(self, req, id, body, filtered_quotas=None,
exclude_server_groups=False):
context = req.environ['nova.context']
context.can(qcs_policies.POLICY_ROOT % 'update', {'quota_class': id})
try:
@@ -99,4 +136,5 @@ class QuotaClassSetsController(wsgi.Controller):
objects.Quotas.create_class(context, quota_class, key, value)
values = QUOTAS.get_class_quotas(context, quota_class)
return self._format_quota_set(None, values, req)
return self._format_quota_set(None, values, filtered_quotas,
exclude_server_groups)
+39 -10
View File
@@ -38,8 +38,12 @@ from nova import quota
CONF = nova.conf.CONF
QUOTAS = quota.QUOTAS
FILTERED_QUOTAS = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
FILTERED_QUOTAS_2_36 = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
FILTERED_QUOTAS_2_57 = list(FILTERED_QUOTAS_2_36)
FILTERED_QUOTAS_2_57.extend(['injected_files', 'injected_file_content_bytes',
'injected_file_path_bytes'])
class QuotaSetsController(wsgi.Controller):
@@ -106,10 +110,16 @@ class QuotaSetsController(wsgi.Controller):
def show(self, req, id):
return self._show(req, id, [])
@wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION) # noqa
@wsgi.Controller.api_version( # noqa
MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56')
@extensions.expected_errors(400)
def show(self, req, id):
return self._show(req, id, FILTERED_QUOTAS)
return self._show(req, id, FILTERED_QUOTAS_2_36)
@wsgi.Controller.api_version('2.57') # noqa
@extensions.expected_errors(400)
def show(self, req, id):
return self._show(req, id, FILTERED_QUOTAS_2_57)
@validation.query_schema(quota_sets.query_schema)
def _show(self, req, id, filtered_quotas):
@@ -128,10 +138,16 @@ class QuotaSetsController(wsgi.Controller):
def detail(self, req, id):
return self._detail(req, id, [])
@wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION) # noqa
@wsgi.Controller.api_version( # noqa
MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56')
@extensions.expected_errors(400)
def detail(self, req, id):
return self._detail(req, id, FILTERED_QUOTAS)
return self._detail(req, id, FILTERED_QUOTAS_2_36)
@wsgi.Controller.api_version('2.57') # noqa
@extensions.expected_errors(400)
def detail(self, req, id):
return self._detail(req, id, FILTERED_QUOTAS_2_57)
@validation.query_schema(quota_sets.query_schema)
def _detail(self, req, id, filtered_quotas):
@@ -151,11 +167,18 @@ class QuotaSetsController(wsgi.Controller):
def update(self, req, id, body):
return self._update(req, id, body, [])
@wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION) # noqa
@wsgi.Controller.api_version( # noqa
MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56')
@extensions.expected_errors(400)
@validation.schema(quota_sets.update_v236)
def update(self, req, id, body):
return self._update(req, id, body, FILTERED_QUOTAS)
return self._update(req, id, body, FILTERED_QUOTAS_2_36)
@wsgi.Controller.api_version('2.57') # noqa
@extensions.expected_errors(400)
@validation.schema(quota_sets.update_v257)
def update(self, req, id, body):
return self._update(req, id, body, FILTERED_QUOTAS_2_57)
@validation.query_schema(quota_sets.query_schema)
def _update(self, req, id, body, filtered_quotas):
@@ -221,10 +244,16 @@ class QuotaSetsController(wsgi.Controller):
def defaults(self, req, id):
return self._defaults(req, id, [])
@wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION) # noqa
@wsgi.Controller.api_version( # noqa
MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56')
@extensions.expected_errors(400)
def defaults(self, req, id):
return self._defaults(req, id, FILTERED_QUOTAS)
return self._defaults(req, id, FILTERED_QUOTAS_2_36)
@wsgi.Controller.api_version('2.57') # noqa
@extensions.expected_errors(400)
def defaults(self, req, id):
return self._defaults(req, id, FILTERED_QUOTAS_2_57)
def _defaults(self, req, id, filtered_quotas):
context = req.environ['nova.context']
@@ -715,3 +715,17 @@ The embedded flavor description will not be included in server representations.
the optional ``host`` string field defaulted to ``null``. If ``host`` is
set the migrate action verifies the provided host with the nova scheduler
and uses it as the destination for the migration.
2.57
----
The 2.57 microversion makes the following changes:
* The ``personality`` parameter is removed from the server create and rebuild
APIs.
* The ``user_data`` parameter is added to the server rebuild API.
* The ``maxPersonality`` and ``maxPersonalitySize`` limits are excluded from
the ``GET /limits`` API response.
* The ``injected_files``, ``injected_file_content_bytes`` and
``injected_file_path_bytes`` quotas are removed from the ``os-quota-sets``
and ``os-quota-class-sets`` APIs.
@@ -36,3 +36,12 @@ del update_v250['properties']['quota_class_set']['properties'][
del update_v250['properties']['quota_class_set']['properties'][
'security_group_rules']
del update_v250['properties']['quota_class_set']['properties']['networks']
# 2.57 builds on 2.50 and removes injected_file* quotas.
update_v257 = copy.deepcopy(update_v250)
del update_v257['properties']['quota_class_set']['properties'][
'injected_files']
del update_v257['properties']['quota_class_set']['properties'][
'injected_file_content_bytes']
del update_v257['properties']['quota_class_set']['properties'][
'injected_file_path_bytes']
@@ -69,6 +69,14 @@ update = {
update_v236 = copy.deepcopy(update)
update_v236['properties']['quota_set']['properties'] = update_quota_set_v236
# 2.57 builds on 2.36 and removes injected_file* quotas.
update_quota_set_v257 = copy.deepcopy(update_quota_set_v236)
del update_quota_set_v257['injected_files']
del update_quota_set_v257['injected_file_content_bytes']
del update_quota_set_v257['injected_file_path_bytes']
update_v257 = copy.deepcopy(update_v236)
update_v257['properties']['quota_set']['properties'] = update_quota_set_v257
query_schema = {
'type': 'object',
'properties': {
@@ -14,6 +14,7 @@
import copy
from nova.api.openstack.compute.schemas import user_data
from nova.api.validation import parameter_types
from nova.api.validation.parameter_types import multi_params
from nova.objects import instance
@@ -139,6 +140,11 @@ base_create_v252['properties']['server']['properties']['tags'] = {
}
# 2.57 builds on 2.52 and removes the personality parameter.
base_create_v257 = copy.deepcopy(base_create_v252)
base_create_v257['properties']['server']['properties'].pop('personality')
base_update = {
'type': 'object',
'properties': {
@@ -203,6 +209,18 @@ base_rebuild_v254 = copy.deepcopy(base_rebuild_v219)
base_rebuild_v254['properties']['rebuild'][
'properties']['key_name'] = parameter_types.name_or_none
# 2.57 builds on 2.54 and makes the following changes:
# 1. Remove the personality parameter.
# 2. Add the user_data parameter which is nullable so user_data can be reset.
base_rebuild_v257 = copy.deepcopy(base_rebuild_v254)
base_rebuild_v257['properties']['rebuild']['properties'].pop('personality')
base_rebuild_v257['properties']['rebuild']['properties']['user_data'] = ({
'oneOf': [
user_data.common_user_data,
{'type': 'null'}
]
})
resize = {
'type': 'object',
'properties': {
+17 -2
View File
@@ -80,11 +80,13 @@ class ServersController(wsgi.Controller):
schema_server_update_v219 = schema_servers.base_update_v219
schema_server_rebuild_v219 = schema_servers.base_rebuild_v219
schema_server_rebuild_v254 = schema_servers.base_rebuild_v254
schema_server_rebuild_v257 = schema_servers.base_rebuild_v257
schema_server_create_v232 = schema_servers.base_create_v232
schema_server_create_v237 = schema_servers.base_create_v237
schema_server_create_v242 = schema_servers.base_create_v242
schema_server_create_v252 = schema_servers.base_create_v252
schema_server_create_v257 = schema_servers.base_create_v257
# NOTE(alex_xu): Please do not add more items into this list. This list
# should be removed in the future.
@@ -134,6 +136,7 @@ class ServersController(wsgi.Controller):
# TODO(alex_xu): The final goal is that merging all of
# extended json-schema into server main json-schema.
self._create_schema(self.schema_server_create_v257, '2.57')
self._create_schema(self.schema_server_create_v252, '2.52')
self._create_schema(self.schema_server_create_v242, '2.42')
self._create_schema(self.schema_server_create_v237, '2.37')
@@ -447,7 +450,8 @@ class ServersController(wsgi.Controller):
@validation.schema(schema_server_create_v232, '2.32', '2.36')
@validation.schema(schema_server_create_v237, '2.37', '2.41')
@validation.schema(schema_server_create_v242, '2.42', '2.51')
@validation.schema(schema_server_create_v252, '2.52')
@validation.schema(schema_server_create_v252, '2.52', '2.56')
@validation.schema(schema_server_create_v257, '2.57')
def create(self, req, body):
"""Creates a new server for a given user."""
context = req.environ['nova.context']
@@ -878,7 +882,8 @@ class ServersController(wsgi.Controller):
@validation.schema(schema_server_rebuild_v20, '2.0', '2.0')
@validation.schema(schema_server_rebuild, '2.1', '2.18')
@validation.schema(schema_server_rebuild_v219, '2.19', '2.53')
@validation.schema(schema_server_rebuild_v254, '2.54')
@validation.schema(schema_server_rebuild_v254, '2.54', '2.56')
@validation.schema(schema_server_rebuild_v257, '2.57')
def _action_rebuild(self, req, id, body):
"""Rebuild an instance with the given attributes."""
rebuild_dict = body['rebuild']
@@ -906,6 +911,13 @@ class ServersController(wsgi.Controller):
and 'key_name' in rebuild_dict):
kwargs['key_name'] = rebuild_dict.get('key_name')
# If user_data is not specified, we don't include it in kwargs because
# we don't want to overwrite the existing user_data.
include_user_data = api_version_request.is_supported(
req, min_version='2.57')
if include_user_data and 'user_data' in rebuild_dict:
kwargs['user_data'] = rebuild_dict['user_data']
for request_attribute, instance_attribute in attr_map.items():
try:
if request_attribute == 'name':
@@ -962,6 +974,9 @@ class ServersController(wsgi.Controller):
# NOTE(liuyulong): set the new key_name for the API response.
view['server']['key_name'] = instance.key_name
if include_user_data:
view['server']['user_data'] = instance.user_data
robj = wsgi.ResponseObject(view)
return self._add_location(robj)
+7 -12
View File
@@ -14,12 +14,6 @@
# under the License.
# This is a list of limits which needs to filter out from the API response.
# This is due to the deprecation of network related proxy APIs, the related
# limit should be removed from the API also.
FILTERED_LIMITS = ['floating_ips', 'security_groups', 'security_group_rules']
class ViewBuilder(object):
"""OpenStack API base limits view builder."""
@@ -41,9 +35,10 @@ class ViewBuilder(object):
"server_group_members": ["maxServerGroupMembers"]
}
def build(self, absolute_limits, filter_result=False, max_image_meta=True):
def build(self, absolute_limits, filtered_limits=None,
max_image_meta=True):
absolute_limits = self._build_absolute_limits(
absolute_limits, filter_result=filter_result,
absolute_limits, filtered_limits,
max_image_meta=max_image_meta)
output = {
@@ -55,17 +50,17 @@ class ViewBuilder(object):
return output
def _build_absolute_limits(self, absolute_limits, filter_result=False,
def _build_absolute_limits(self, absolute_limits, filtered_limits=None,
max_image_meta=True):
"""Builder for absolute limits
absolute_limits should be given as a dict of limits.
For example: {"ram": 512, "gigabytes": 1024}.
filtered_limits is an optional list of limits to exclude from the
result set.
"""
filtered_limits = []
if filter_result:
filtered_limits = FILTERED_LIMITS
filtered_limits = filtered_limits or []
limits = {}
for name, value in absolute_limits.items():
if (name in self.limit_names and
@@ -0,0 +1,18 @@
{
"limits": {
"absolute": {
"maxServerMeta": 128,
"maxTotalCores": 20,
"maxTotalInstances": 10,
"maxTotalKeypairs": 100,
"maxTotalRAMSize": 51200,
"maxServerGroups": 10,
"maxServerGroupMembers": 10,
"totalCoresUsed": 0,
"totalInstancesUsed": 0,
"totalRAMUsed": 0,
"totalServerGroupsUsed": 0
},
"rate": []
}
}
@@ -0,0 +1,12 @@
{
"quota_class_set": {
"cores": 20,
"id": "%(set_id)s",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,11 @@
{
"quota_class_set": {
"instances": 50,
"cores": 50,
"ram": 51200,
"metadata_items": 128,
"key_pairs": 100,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,11 @@
{
"quota_class_set": {
"cores": 50,
"instances": 50,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,12 @@
{
"quota_set": {
"cores": 20,
"id": "fake_tenant",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,40 @@
{
"quota_set": {
"cores": {
"in_use": 0,
"limit": 20,
"reserved": 0
},
"id": "fake_tenant",
"instances": {
"in_use": 0,
"limit": 10,
"reserved": 0
},
"key_pairs": {
"in_use": 0,
"limit": 100,
"reserved": 0
},
"metadata_items": {
"in_use": 0,
"limit": 128,
"reserved": 0
},
"ram": {
"in_use": 0,
"limit": 51200,
"reserved": 0
},
"server_group_members": {
"in_use": 0,
"limit": 10,
"reserved": 0
},
"server_groups": {
"in_use": 0,
"limit": 10,
"reserved": 0
}
}
}
@@ -0,0 +1,12 @@
{
"quota_set": {
"cores": 20,
"id": "fake_tenant",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,6 @@
{
"quota_set": {
"force": "True",
"instances": 45
}
}
@@ -0,0 +1,11 @@
{
"quota_set": {
"cores": 20,
"instances": 45,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,5 @@
{
"quota_set": {
"instances": 20
}
}
@@ -0,0 +1,11 @@
{
"quota_set": {
"cores": 20,
"instances": 20,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,12 @@
{
"quota_set": {
"cores": 20,
"id": "fake_tenant",
"instances": 10,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,5 @@
{
"quota_set": {
"instances": 9
}
}
@@ -0,0 +1,11 @@
{
"quota_set": {
"cores": 20,
"instances": 9,
"key_pairs": 100,
"metadata_items": 128,
"ram": 51200,
"server_groups": 10,
"server_group_members": 10
}
}
@@ -0,0 +1,61 @@
{
"server": {
"accessIPv4": "%(access_ip_v4)s",
"accessIPv6": "%(access_ip_v6)s",
"addresses": {
"private": [
{
"addr": "%(ip)s",
"version": 4
}
]
},
"adminPass": "%(password)s",
"created": "%(isotime)s",
"flavor": {
"disk": 1,
"ephemeral": 0,
"extra_specs": {},
"original_name": "m1.tiny",
"ram": 512,
"swap": 0,
"vcpus": 1
},
"hostId": "%(hostid)s",
"id": "%(uuid)s",
"image": {
"id": "%(uuid)s",
"links": [
{
"href": "%(compute_endpoint)s/images/%(uuid)s",
"rel": "bookmark"
}
]
},
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(uuid)s",
"rel": "bookmark"
}
],
"locked": false,
"metadata": {
"meta_var": "meta_val"
},
"name": "%(name)s",
"key_name": "%(key_name)s",
"description": "%(description)s",
"progress": 0,
"OS-DCF:diskConfig": "AUTO",
"status": "ACTIVE",
"tenant_id": "6f70656e737461636b20342065766572",
"updated": "%(isotime)s",
"user_id": "fake",
"tags": [],
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}
@@ -0,0 +1,15 @@
{
"rebuild" : {
"accessIPv4" : "%(access_ip_v4)s",
"accessIPv6" : "%(access_ip_v6)s",
"imageRef" : "%(uuid)s",
"name" : "%(name)s",
"key_name" : "%(key_name)s",
"description" : "%(description)s",
"adminPass" : "%(pass)s",
"metadata" : {
"meta_var" : "meta_val"
},
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}
@@ -0,0 +1,21 @@
{
"server" : {
"accessIPv4": "%(access_ip_v4)s",
"accessIPv6": "%(access_ip_v6)s",
"name" : "new-server-test",
"imageRef" : "%(image_id)s",
"flavorRef" : "http://openstack.example.com/flavors/1",
"availability_zone": "nova",
"OS-DCF:diskConfig": "AUTO",
"metadata" : {
"My Server Name" : "Apache1"
},
"security_groups": [
{
"name": "default"
}
],
"user_data" : "%(user_data)s",
"networks": "auto"
}
}
@@ -0,0 +1,22 @@
{
"server": {
"OS-DCF:diskConfig": "AUTO",
"adminPass": "%(password)s",
"id": "%(id)s",
"links": [
{
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
"rel": "self"
},
{
"href": "%(compute_endpoint)s/servers/%(uuid)s",
"rel": "bookmark"
}
],
"security_groups": [
{
"name": "default"
}
]
}
}
@@ -65,3 +65,15 @@ class LimitsV239Test(api_sample_base.ApiSampleTestBaseV21):
self.api.microversion = self.microversion
response = self._do_get('limits')
self._verify_response('limit-get-resp', {}, response, 200)
class LimitsV257Test(api_sample_base.ApiSampleTestBaseV21):
"""Test limits don't return maxPersonality* fields after 2.57."""
sample_dir = "limits"
microversion = '2.57'
scenarios = [('v2_57', {'api_major_version': 'v2.1'})]
def test_limits_get(self):
self.api.microversion = self.microversion
response = self._do_get('limits')
self._verify_response('limit-get-resp', {}, response, 200)
@@ -40,3 +40,8 @@ class QuotaClassesSampleJsonTests(api_sample_base.ApiSampleTestBaseV21):
class QuotaClassesV250SampleJsonTests(QuotaClassesSampleJsonTests):
microversion = '2.50'
scenarios = [('v2_50', {'api_major_version': 'v2.1'})]
class QuotaClassesV257SampleJsonTests(QuotaClassesSampleJsonTests):
microversion = '2.57'
scenarios = [('v2_57', {'api_major_version': 'v2.1'})]
@@ -83,6 +83,14 @@ class QuotaSetsSampleJsonTests2_36(QuotaSetsSampleJsonTests):
scenarios = [('v2_36', {'api_major_version': 'v2.1'})]
class QuotaSetsSampleJsonTestsV2_57(QuotaSetsSampleJsonTests):
"""Tests that injected_file* quotas are not in request or response values.
starting with microversion 2.57.
"""
microversion = '2.57'
scenarios = [('v2_57', {'api_major_version': 'v2.1'})]
class NoopQuotaSetsSampleJsonTests(QuotaSetsSampleJsonTests):
sample_dir = "os-quota-sets-noop"
@@ -463,9 +463,12 @@ class ServersActionsJson254Test(ServersSampleBase):
sample_dir = 'servers'
scenarios = [('v2_54', {'api_major_version': 'v2.1'})]
def _create_server(self):
return self._post_server()
def test_server_rebuild(self):
fakes.stub_out_key_pair_funcs(self)
uuid = self._post_server()
uuid = self._create_server()
image = fake.get_valid_image_id()
params = {
'uuid': image,
@@ -485,6 +488,15 @@ class ServersActionsJson254Test(ServersSampleBase):
self._verify_response('server-action-rebuild-resp', subs, resp, 202)
class ServersActionsJson257Test(ServersActionsJson254Test):
"""Tests rebuilding a server with new user_data."""
microversion = '2.57'
scenarios = [('v2_57', {'api_major_version': 'v2.1'})]
def _create_server(self):
return self._post_server(use_common_server_api_samples=False)
class ServersCreateImageJsonTest(ServersSampleBase,
_ServersActionsJsonTestMixin):
"""Tests the createImage server action API against 2.1."""
@@ -33,6 +33,7 @@ class QuotaClassSetsTestV21(test.TestCase):
'security_groups': 10,
'security_group_rules': 20, 'key_pairs': 100,
'injected_file_path_bytes': 255}
filtered_quotas = None
def quota_set(self, class_name):
quotas = copy.deepcopy(self.quota_resources)
@@ -58,18 +59,15 @@ class QuotaClassSetsTestV21(test.TestCase):
def test_format_quota_set(self):
quota_set = self.controller._format_quota_set('test_class',
self.quota_resources,
self.req)
self.filtered_quotas)
qs = quota_set['quota_class_set']
self.assertEqual(qs['id'], 'test_class')
self.assertEqual(qs['instances'], 10)
self.assertEqual(qs['cores'], 20)
self.assertEqual(qs['ram'], 51200)
self.assertEqual(qs['metadata_items'], 128)
self.assertEqual(qs['injected_files'], 5)
self.assertEqual(qs['injected_file_path_bytes'], 255)
self.assertEqual(qs['injected_file_content_bytes'], 10240)
self.assertEqual(qs['key_pairs'], 100)
for resource, value in self.quota_resources.items():
self.assertEqual(value, qs[resource])
if self.filtered_quotas:
for resource in self.filtered_quotas:
self.assertNotIn(resource, qs)
self._check_filtered_extended_quota(qs)
def test_quotas_show(self):
@@ -135,25 +133,31 @@ class QuotaClassSetsTestV250(QuotaClassSetsTestV21):
'injected_file_path_bytes': 255,
'server_groups': 10,
'server_group_members': 10}
filtered_quotas = quota_classes_v21.FILTERED_QUOTAS_2_50
def _check_filtered_extended_quota(self, quota_set):
self.assertEqual(10, quota_set['server_groups'])
self.assertEqual(10, quota_set['server_group_members'])
self.assertNotIn('floating_ips', quota_set)
self.assertNotIn('fixed_ips', quota_set)
self.assertNotIn('security_groups', quota_set)
self.assertNotIn('security_group_rules', quota_set)
self.assertNotIn('networks', quota_set)
for resource in self.filtered_quotas:
self.assertNotIn(resource, quota_set)
def test_quotas_update_with_filtered_quota(self):
filtered_quotas = ["fixed_ips", "floating_ips", "networks",
"security_group_rules", "security_groups"]
for resource in filtered_quotas:
for resource in self.filtered_quotas:
body = {'quota_class_set': {resource: 10}}
self.assertRaises(self.validation_error, self.controller.update,
self.req, 'test_class', body=body)
class QuotaClassSetsTestV257(QuotaClassSetsTestV250):
api_version = '2.57'
def setUp(self):
super(QuotaClassSetsTestV257, self).setUp()
for resource in quota_classes_v21.FILTERED_QUOTAS_2_57:
self.quota_resources.pop(resource, None)
self.filtered_quotas.extend(quota_classes_v21.FILTERED_QUOTAS_2_57)
class QuotaClassesPolicyEnforcementV21(test.NoDBTestCase):
def setUp(self):
@@ -564,6 +564,7 @@ class QuotaSetsPolicyEnforcementV21(test.NoDBTestCase):
class QuotaSetsTestV236(test.NoDBTestCase):
microversion = '2.36'
def setUp(self):
super(QuotaSetsTestV236, self).setUp()
@@ -613,7 +614,7 @@ class QuotaSetsTestV236(test.NoDBTestCase):
'server_groups': 10
}
self.controller = quotas_v21.QuotaSetsController()
self.req = fakes.HTTPRequest.blank('', version='2.36')
self.req = fakes.HTTPRequest.blank('', version=self.microversion)
def _ensure_filtered_quotas_existed_in_old_api(self):
res_dict = self.controller.show(self.old_req, 1234)
@@ -666,3 +667,11 @@ class QuotaSetsTestV236(test.NoDBTestCase):
body={'quota_set': {'cores': 100}})
for filtered in self.filtered_quotas:
self.assertNotIn(filtered, res_dict['quota_set'])
class QuotaSetsTestV257(QuotaSetsTestV236):
microversion = '2.57'
def setUp(self):
super(QuotaSetsTestV257, self).setUp()
self.filtered_quotas.extend(quotas_v21.FILTERED_QUOTAS_2_57)
@@ -2295,6 +2295,105 @@ class ServersControllerRebuildTestV254(ServersControllerRebuildInstanceTest):
self.req, FAKE_UUID, body=body)
class ServersControllerRebuildTestV257(ServersControllerRebuildTestV254):
"""Tests server rebuild at microversion 2.57 where user_data can be
provided and personality files are no longer accepted.
"""
def setUp(self):
super(ServersControllerRebuildTestV257, self).setUp()
self.req.api_version_request = \
api_version_request.APIVersionRequest('2.57')
def test_rebuild_personality(self):
"""Tests that trying to rebuild with personality files fails."""
body = {
"rebuild": {
"imageRef": self.image_uuid,
"personality": [{
"path": "/path/to/file",
"contents": base64.encode_as_text("Test String"),
}]
}
}
ex = self.assertRaises(exception.ValidationError,
self.controller._action_rebuild,
self.req, FAKE_UUID, body=body)
self.assertIn('personality', six.text_type(ex))
def test_rebuild_user_data_old_version(self):
"""Tests that trying to rebuild with user_data before 2.57 fails."""
body = {
"rebuild": {
"imageRef": self.image_uuid,
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi"
}
}
self.req.api_version_request = \
api_version_request.APIVersionRequest('2.55')
ex = self.assertRaises(exception.ValidationError,
self.controller._action_rebuild,
self.req, FAKE_UUID, body=body)
self.assertIn('user_data', six.text_type(ex))
def test_rebuild_user_data_malformed(self):
"""Tests that trying to rebuild with malformed user_data fails."""
body = {
"rebuild": {
"imageRef": self.image_uuid,
"user_data": b'invalid'
}
}
ex = self.assertRaises(exception.ValidationError,
self.controller._action_rebuild,
self.req, FAKE_UUID, body=body)
self.assertIn('user_data', six.text_type(ex))
def test_rebuild_user_data_too_large(self):
"""Tests that passing user_data to rebuild that is too large fails."""
body = {
"rebuild": {
"imageRef": self.image_uuid,
"user_data": ('MQ==' * 16384)
}
}
ex = self.assertRaises(exception.ValidationError,
self.controller._action_rebuild,
self.req, FAKE_UUID, body=body)
self.assertIn('user_data', six.text_type(ex))
@mock.patch.object(context.RequestContext, 'can')
@mock.patch.object(compute_api.API, 'get')
@mock.patch('nova.db.instance_update_and_get_original')
def test_rebuild_reset_user_data(self, mock_update, mock_get, mock_policy):
"""Tests that passing user_data=None resets the user_data on the
instance.
"""
body = {
"rebuild": {
"imageRef": self.image_uuid,
"user_data": None
}
}
mock_get.return_value = fakes.stub_instance_obj(
context.RequestContext(self.req_user_id, self.req_project_id),
user_data='ZWNobyAiaGVsbG8gd29ybGQi')
def fake_instance_update_and_get_original(
ctxt, instance_uuid, values, **kwargs):
# save() is called twice and the second one has system_metadata
# in the updates, so we can ignore that one.
if 'system_metadata' not in values:
self.assertIn('user_data', values)
self.assertIsNone(values['user_data'])
return instance_update_and_get_original(
ctxt, instance_uuid, values, **kwargs)
mock_update.side_effect = fake_instance_update_and_get_original
self.controller._action_rebuild(self.req, FAKE_UUID, body=body)
self.assertEqual(2, mock_update.call_count)
class ServersControllerRebuildTestV219(ServersControllerRebuildInstanceTest):
def setUp(self):
@@ -4096,6 +4195,33 @@ class ServersControllerCreateTestV252(test.NoDBTestCase):
exception.ValidationError, self._create_server, tags)
class ServersControllerCreateTestV257(test.NoDBTestCase):
"""Tests that trying to create a server with personality files using
microversion 2.57 fails.
"""
def test_create_server_with_personality_fails(self):
controller = servers.ServersController()
body = {
'server': {
'name': 'no-personality-files',
'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378',
'flavorRef': '2',
'networks': 'auto',
'personality': [{
'path': '/path/to/file',
'contents': 'ZWNobyAiaGVsbG8gd29ybGQi'
}]
}
}
req = fakes.HTTPRequestV21.blank('/servers', version='2.57')
req.body = jsonutils.dump_as_bytes(body)
req.method = 'POST'
req.headers['content-type'] = 'application/json'
ex = self.assertRaises(
exception.ValidationError, controller.create, req, body=body)
self.assertIn('personality', six.text_type(ex))
class ServersControllerCreateTestWithMock(test.TestCase):
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
flavor_ref = 'http://localhost/123/flavors/3'
@@ -0,0 +1,17 @@
---
features:
- |
The 2.57 microversion makes the following changes:
* The ``user_data`` parameter is added to the server rebuild API.
* The ``personality`` parameter is removed from the server create and
rebuild APIs. Use the ``user_data`` parameter instead.
* The ``maxPersonality`` and ``maxPersonalitySize`` limits are excluded
from the ``GET /limits`` API response.
* The ``injected_files``, ``injected_file_content_bytes`` and
``injected_file_path_bytes`` quotas are removed from the
``os-quota-sets`` and ``os-quota-class-sets`` APIs.
See the `spec`_ for more details and reasoning.
.. _spec: https://specs.openstack.org/openstack/nova-specs/specs/queens/approved/deprecate-file-injection.html