api: Add response body schemas for remaining server action APIs

This demonstrates far more complex response schemas, including the
response to the rebuild action which is effectively the response to the
server show API.

Change-Id: I6dc355f3c3f164d0bc7887a58e8b13979f0b476e
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane 2024-03-26 22:02:33 +00:00 committed by Ghanshyam Mann
parent 908d9263ee
commit b967f2a693
25 changed files with 900 additions and 174 deletions

View File

@ -2,7 +2,7 @@
"flavor_access": [
{
"flavor_id": "10",
"tenant_id": "fake_tenant"
"tenant_id": "6f70656e737461636b20342065766572"
}
]
}

View File

@ -2,7 +2,7 @@
"flavor_access": [
{
"flavor_id": "10",
"tenant_id": "fake_tenant"
"tenant_id": "6f70656e737461636b20342065766572"
}
]
}

View File

@ -13,7 +13,7 @@
"extra_specs": {
"hw:numa_nodes": "2"
},
"projects": ["fake_tenant"],
"projects": ["6f70656e737461636b20342065766572"],
"swap": 0,
"rxtx_factor": 2.0,
"is_public": false,

View File

@ -19,7 +19,7 @@ import re
import webob
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import console_output
from nova.api.openstack.compute.schemas import console_output as schema
from nova.api.openstack import wsgi
from nova.api import validation
from nova.compute import api as compute
@ -34,7 +34,8 @@ class ConsoleOutputController(wsgi.Controller):
@wsgi.expected_errors((404, 409, 501))
@wsgi.action('os-getConsoleOutput')
@validation.schema(console_output.get_console_output)
@validation.schema(schema.get_console_output)
@validation.response_body_schema(schema.get_console_output_response)
def get_console_output(self, req, id, body):
"""Get text console output."""
context = req.environ['nova.context']

View File

@ -17,7 +17,7 @@ import webob
from nova.api.openstack import api_version_request
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import create_backup
from nova.api.openstack.compute.schemas import create_backup as schema
from nova.api.openstack import wsgi
from nova.api import validation
from nova.compute import api as compute
@ -33,8 +33,14 @@ class CreateBackupController(wsgi.Controller):
@wsgi.response(202)
@wsgi.expected_errors((400, 403, 404, 409))
@wsgi.action('createBackup')
@validation.schema(create_backup.create_backup_v20, '2.0', '2.0')
@validation.schema(create_backup.create_backup, '2.1')
@validation.schema(schema.create_backup_v20, '2.0', '2.0')
@validation.schema(schema.create_backup, '2.1')
@validation.response_body_schema(
schema.create_backup_response, '2.1', '2.44',
)
@validation.response_body_schema(
schema.create_backup_response_v245, '2.45'
)
def _create_backup(self, req, id, body):
"""Backup a server instance.

View File

@ -80,9 +80,13 @@ class EvacuateController(wsgi.Controller):
@wsgi.action('evacuate')
@validation.schema(evacuate.evacuate, "2.0", "2.13")
@validation.schema(evacuate.evacuate_v214, "2.14", "2.28")
@validation.schema(evacuate.evacuate_v2_29, "2.29", "2.67")
@validation.schema(evacuate.evacuate_v2_68, "2.68", "2.94")
@validation.schema(evacuate.evacuate_v2_95, "2.95")
@validation.schema(evacuate.evacuate_v229, "2.29", "2.67")
@validation.schema(evacuate.evacuate_v268, "2.68", "2.94")
@validation.schema(evacuate.evacuate_v295, "2.95")
@validation.response_body_schema(
evacuate.evacuate_response, "2.0", "2.13"
)
@validation.response_body_schema(evacuate.evacuate_response_v214, "2.14")
def _evacuate(self, req, id, body):
"""Permit admins to evacuate a server from a failed host
to a new one.

View File

@ -63,6 +63,7 @@ class FlavorActionController(wsgi.Controller):
@wsgi.expected_errors((400, 403, 404, 409))
@wsgi.action("addTenantAccess")
@validation.schema(schema.add_tenant_access)
@validation.response_body_schema(schema.add_tenant_access_response)
def _add_tenant_access(self, req, id, body):
context = req.environ['nova.context']
context.can(fa_policies.POLICY_ROOT % "add_tenant_access", target={})
@ -88,6 +89,7 @@ class FlavorActionController(wsgi.Controller):
@wsgi.expected_errors((400, 403, 404))
@wsgi.action("removeTenantAccess")
@validation.schema(schema.remove_tenant_access)
@validation.response_body_schema(schema.remove_tenant_access_response)
def _remove_tenant_access(self, req, id, body):
context = req.environ['nova.context']
context.can(

View File

@ -15,7 +15,7 @@
import webob
from nova.api.openstack import common
from nova.api.openstack.compute.schemas import remote_consoles
from nova.api.openstack.compute.schemas import remote_consoles as schema
from nova.api.openstack import wsgi
from nova.api import validation
from nova.compute import api as compute
@ -40,7 +40,8 @@ class RemoteConsolesController(wsgi.Controller):
@wsgi.Controller.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getVNCConsole')
@validation.schema(remote_consoles.get_vnc_console)
@validation.schema(schema.get_vnc_console)
@validation.response_body_schema(schema.get_vnc_console_response)
def get_vnc_console(self, req, id, body):
"""Get text console output."""
context = req.environ['nova.context']
@ -71,7 +72,8 @@ class RemoteConsolesController(wsgi.Controller):
@wsgi.Controller.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getSPICEConsole')
@validation.schema(remote_consoles.get_spice_console)
@validation.schema(schema.get_spice_console)
@validation.response_body_schema(schema.get_spice_console_response)
def get_spice_console(self, req, id, body):
"""Get text console output."""
context = req.environ['nova.context']
@ -100,7 +102,7 @@ class RemoteConsolesController(wsgi.Controller):
@wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getRDPConsole')
@wsgi.removed('29.0.0', _rdp_console_removal_reason)
@validation.schema(remote_consoles.get_rdp_console)
@validation.schema(schema.get_rdp_console)
def get_rdp_console(self, req, id, body):
"""RDP console was available only for HyperV driver which has been
removed from Nova in 29.0.0 (Caracal) release.
@ -110,7 +112,8 @@ class RemoteConsolesController(wsgi.Controller):
@wsgi.Controller.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getSerialConsole')
@validation.schema(remote_consoles.get_serial_console)
@validation.schema(schema.get_serial_console)
@validation.response_body_schema(schema.get_serial_console_response)
def get_serial_console(self, req, id, body):
"""Get connection to a serial console."""
context = req.environ['nova.context']
@ -139,8 +142,8 @@ class RemoteConsolesController(wsgi.Controller):
@wsgi.Controller.api_version("2.6")
@wsgi.expected_errors((400, 404, 409, 501))
@validation.schema(remote_consoles.create_v26, "2.6", "2.7")
@validation.schema(remote_consoles.create_v28, "2.8")
@validation.schema(schema.create_v26, "2.6", "2.7")
@validation.schema(schema.create_v28, "2.8")
def create(self, req, server_id, body):
context = req.environ['nova.context']
instance = common.get_instance(self.compute_api, context, server_id)

View File

@ -34,3 +34,12 @@ get_console_output = {
'required': ['os-getConsoleOutput'],
'additionalProperties': False,
}
get_console_output_response = {
'type': 'object',
'properties': {
'output': {'type': 'string'},
},
'required': ['output'],
'additionalProperties': False,
}

View File

@ -41,5 +41,19 @@ create_backup = {
create_backup_v20 = copy.deepcopy(create_backup)
create_backup_v20['properties'][
'createBackup']['properties']['name'] = (parameter_types.
name_with_leading_trailing_spaces)
'createBackup']['properties']['name'] = (
parameter_types.name_with_leading_trailing_spaces)
create_backup_response = {
'type': 'null',
}
create_backup_response_v245 = {
'type': 'object',
'properties': {
'image_id': {'type': 'string', 'format': 'uuid'},
},
'required': ['image_id'],
'additionalProperties': False,
}

View File

@ -39,14 +39,31 @@ evacuate_v214 = copy.deepcopy(evacuate)
del evacuate_v214['properties']['evacuate']['properties']['onSharedStorage']
del evacuate_v214['properties']['evacuate']['required']
evacuate_v2_29 = copy.deepcopy(evacuate_v214)
evacuate_v2_29['properties']['evacuate']['properties'][
evacuate_v229 = copy.deepcopy(evacuate_v214)
evacuate_v229['properties']['evacuate']['properties'][
'force'] = parameter_types.boolean
# v2.68 removes the 'force' parameter added in v2.29, meaning it is identical
# to v2.14
evacuate_v2_68 = copy.deepcopy(evacuate_v214)
evacuate_v268 = copy.deepcopy(evacuate_v214)
# v2.95 keeps the same schema, evacuating an instance will now result its state
# to be stopped at destination.
evacuate_v2_95 = copy.deepcopy(evacuate_v2_68)
evacuate_v295 = copy.deepcopy(evacuate_v268)
evacuate_response = {
'type': ['object', 'null'],
'properties': {
'adminPass': {
'type': ['null', 'string'],
}
},
# adminPass is a rare-example of configuration-driven API behavior: the
# value depends on '[api] enable_instance_password'
'required': [],
'additionalProperties': False,
}
evacuate_response_v214 = {
'type': 'null',
}

View File

@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
add_tenant_access = {
'type': 'object',
'properties': {
@ -31,7 +33,6 @@ add_tenant_access = {
'additionalProperties': False,
}
remove_tenant_access = {
'type': 'object',
'properties': {
@ -57,3 +58,27 @@ index_query = {
'properties': {},
'additionalProperties': True,
}
_common_response = {
'type': 'object',
'properties': {
'flavor_access': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'flavor_id': {'type': 'string'},
'tenant_id': {'type': 'string', 'format': 'uuid'},
},
'required': ['flavor_id', 'tenant_id'],
'additionalProperties': True,
},
},
},
'required': ['flavor_access'],
'additionalProperties': True,
}
add_tenant_access_response = copy.deepcopy(_common_response)
remove_tenant_access_response = copy.deepcopy(_common_response)

View File

@ -119,3 +119,78 @@ create_v28 = {
'required': ['remote_console'],
'additionalProperties': False,
}
get_vnc_console_response = {
'type': 'object',
'properties': {
'console': {
'type': 'object',
'properties': {
'type': {
'type': 'string',
'enum': ['novnc', 'xvpvnc'],
'description': '',
},
'url': {
'type': 'string',
'format': 'uri',
'description': '',
},
},
'required': ['type', 'url'],
'additionalProperties': False,
},
},
'required': ['console'],
'additionalProperties': False,
}
get_spice_console_response = {
'type': 'object',
'properties': {
'console': {
'type': 'object',
'properties': {
'type': {
'type': 'string',
'enum': ['spice-html5'],
'description': '',
},
'url': {
'type': 'string',
'format': 'uri',
'description': '',
},
},
'required': ['type', 'url'],
'additionalProperties': False,
},
},
'required': ['console'],
'additionalProperties': False,
}
get_serial_console_response = {
'type': 'object',
'properties': {
'console': {
'type': 'object',
'properties': {
'type': {
'type': 'string',
'enum': ['serial'],
'description': '',
},
'url': {
'type': 'string',
'format': 'uri',
'description': '',
},
},
'required': ['type', 'url'],
'additionalProperties': False,
},
},
'required': ['console'],
'additionalProperties': False,
}

View File

@ -238,8 +238,9 @@ create_v20['properties']['server']['properties'][
'security_groups']['items']['properties']['name'] = (
parameter_types.name_with_leading_trailing_spaces)
create_v20['properties']['server']['properties']['user_data'] = {
'oneOf': [{'type': 'string', 'format': 'base64', 'maxLength': 65535},
{'type': 'null'},
'oneOf': [
{'type': 'string', 'format': 'base64', 'maxLength': 65535},
{'type': 'null'},
],
}
@ -282,45 +283,49 @@ create_v237 = copy.deepcopy(create_v233)
create_v237['properties']['server']['required'].append('networks')
create_v237['properties']['server']['properties']['networks'] = {
'oneOf': [
{'type': 'array',
'items': {
'type': 'object',
'properties': {
'fixed_ip': parameter_types.ip_address,
'port': {
'oneOf': [{'type': 'string', 'format': 'uuid'},
{'type': 'null'}]
},
'uuid': {'type': 'string', 'format': 'uuid'},
},
'additionalProperties': False,
},
{
'type': 'array',
'items': {
'type': 'object',
'properties': {
'fixed_ip': parameter_types.ip_address,
'port': {
'oneOf': [{'type': 'string', 'format': 'uuid'},
{'type': 'null'}]
},
'uuid': {'type': 'string', 'format': 'uuid'},
},
'additionalProperties': False,
},
},
{'type': 'string', 'enum': ['none', 'auto']},
]}
],
}
# 2.42 builds on 2.37 and re-introduces the tag field to the list of network
# objects.
create_v242 = copy.deepcopy(create_v237)
create_v242['properties']['server']['properties']['networks'] = {
'oneOf': [
{'type': 'array',
'items': {
'type': 'object',
'properties': {
'fixed_ip': parameter_types.ip_address,
'port': {
'oneOf': [{'type': 'string', 'format': 'uuid'},
{'type': 'null'}]
},
'uuid': {'type': 'string', 'format': 'uuid'},
'tag': parameter_types.tag,
},
'additionalProperties': False,
},
{
'type': 'array',
'items': {
'type': 'object',
'properties': {
'fixed_ip': parameter_types.ip_address,
'port': {
'oneOf': [{'type': 'string', 'format': 'uuid'},
{'type': 'null'}]
},
'uuid': {'type': 'string', 'format': 'uuid'},
'tag': parameter_types.tag,
},
'additionalProperties': False,
},
},
{'type': 'string', 'enum': ['none', 'auto']},
]}
],
}
create_v242['properties']['server'][
'properties']['block_device_mapping_v2']['items'][
'properties']['tag'] = parameter_types.tag
@ -465,7 +470,6 @@ rebuild_v294 = copy.deepcopy(rebuild_v290)
rebuild_v294['properties']['rebuild']['properties'][
'hostname'] = parameter_types.fqdn
resize = {
'type': 'object',
'properties': {
@ -771,3 +775,458 @@ stop_server_response = {
trigger_crash_dump_response = {
'type': 'null',
}
create_image_response = {
'type': 'null',
}
create_image_response_v245 = {
'type': 'object',
'properties': {
'image_id': {'type': 'string', 'format': 'uuid'},
},
'required': ['image_id'],
'additionalProperties': False,
}
rebuild_response = {
'type': 'object',
'properties': {
'server': {
'type': 'object',
'properties': {
'accessIPv4': {
'type': 'string',
'oneOf': [
{'format': 'ipv4'},
{'const': ''},
],
},
'accessIPv6': {
'type': 'string',
'oneOf': [
{'format': 'ipv6'},
{'const': ''},
],
},
'addresses': {
'type': 'object',
'patternProperties': {
'^.+$': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'addr': {
'type': 'string',
'oneOf': [
{'format': 'ipv4'},
{'format': 'ipv6'},
],
},
'version': {
'type': 'number',
'enum': [4, 6],
},
},
'required': [
'addr',
'version'
],
'additionalProperties': False,
},
},
},
'additionalProperties': False,
},
'adminPass': {'type': ['null', 'string']},
'created': {'type': 'string', 'format': 'date-time'},
'fault': {
'type': 'object',
'properties': {
'code': {'type': 'integer'},
'created': {'type': 'string', 'format': 'date-time'},
'details': {'type': 'string'},
'message': {'type': 'string'},
},
'required': ['code', 'created', 'message'],
'additionalProperties': False,
},
'flavor': {
'type': 'object',
'properties': {
'id': {
'type': 'string',
},
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {
'type': 'string',
'format': 'uri',
},
'rel': {
'type': 'string',
},
},
'required': [
'href',
'rel'
],
"additionalProperties": False,
},
},
},
'additionalProperties': False,
},
'hostId': {'type': 'string'},
'id': {'type': 'string'},
'image': {
'oneOf': [
{
'type': 'string',
'const': '',
},
{
'type': 'object',
'properties': {
'id': {
'type': 'string'
},
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {
'type': 'string',
'format': 'uri',
},
'rel': {
'type': 'string',
},
},
'required': [
'href',
'rel'
],
"additionalProperties": False,
},
},
},
'additionalProperties': False,
},
],
},
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {
'type': 'string',
'format': 'uri',
},
'rel': {
'type': 'string',
},
},
'required': [
'href',
'rel'
],
'additionalProperties': False,
},
},
'metadata': {
'type': 'object',
'patternProperties': {
'^.+$': {
'type': 'string'
},
},
'additionalProperties': False,
},
'name': {'type': ['string', 'null']},
'progress': {'type': ['null', 'number']},
'status': {'type': 'string'},
'tenant_id': {'type': 'string', 'format': 'uuid'},
'updated': {'type': 'string', 'format': 'date-time'},
'user_id': {'type': 'string'},
'OS-DCF:diskConfig': {'type': 'string'},
},
'required': [
'accessIPv4',
'accessIPv6',
'addresses',
'created',
'flavor',
'hostId',
'id',
'image',
'links',
'metadata',
'name',
'progress',
'status',
'tenant_id',
'updated',
'user_id',
'OS-DCF:diskConfig',
],
'additionalProperties': False,
},
},
'required': [
'server'
],
'additionalProperties': False,
}
rebuild_response_v29 = copy.deepcopy(rebuild_response)
rebuild_response_v29['properties']['server']['properties']['locked'] = {
'type': 'boolean',
}
rebuild_response_v29['properties']['server']['required'].append('locked')
rebuild_response_v219 = copy.deepcopy(rebuild_response_v29)
rebuild_response_v219['properties']['server']['properties']['description'] = {
'type': ['null', 'string'],
}
rebuild_response_v219['properties']['server']['required'].append('description')
rebuild_response_v226 = copy.deepcopy(rebuild_response_v219)
rebuild_response_v226['properties']['server']['properties']['tags'] = {
'type': 'array',
'items': {
'type': 'string',
},
'maxItems': 50,
}
rebuild_response_v226['properties']['server']['required'].append('tags')
# NOTE(stephenfin): We overwrite rather than extend 'flavor', since we now
# embed the flavor in this version
rebuild_response_v246 = copy.deepcopy(rebuild_response_v226)
rebuild_response_v246['properties']['server']['properties']['flavor'] = {
'type': 'object',
'properties': {
'vcpus': {
'type': 'integer',
},
'ram': {
'type': 'integer',
},
'disk': {
'type': 'integer',
},
'ephemeral': {
'type': 'integer',
},
'swap': {
'type': 'integer',
},
'original_name': {
'type': 'string',
},
'extra_specs': {
'type': 'object',
'patternProperties': {
'^.+$': {
'type': 'string'
},
},
'additionalProperties': False,
},
},
'required': ['vcpus', 'ram', 'disk', 'ephemeral', 'swap', 'original_name'],
'additionalProperties': False,
}
rebuild_response_v254 = copy.deepcopy(rebuild_response_v246)
rebuild_response_v254['properties']['server']['properties']['key_name'] = {
'type': ['null', 'string'],
}
rebuild_response_v254['properties']['server']['required'].append('key_name')
rebuild_response_v257 = copy.deepcopy(rebuild_response_v254)
rebuild_response_v257['properties']['server']['properties']['user_data'] = {
'oneOf': [
{'type': 'string', 'format': 'base64', 'maxLength': 65535},
{'type': 'null'},
],
}
rebuild_response_v257['properties']['server']['required'].append('user_data')
rebuild_response_v263 = copy.deepcopy(rebuild_response_v257)
rebuild_response_v263['properties']['server']['properties'].update(
{
'trusted_image_certificates': {
'type': ['array', 'null'],
'items': {
'type': 'string',
},
},
},
)
rebuild_response_v263['properties']['server']['required'].append(
'trusted_image_certificates'
)
rebuild_response_v271 = copy.deepcopy(rebuild_response_v263)
rebuild_response_v271['properties']['server']['properties'].update(
{
'server_groups': {
'type': 'array',
'items': {
'type': 'string',
'format': 'uuid',
},
'maxLength': 1,
},
},
)
rebuild_response_v271['properties']['server']['required'].append(
'server_groups'
)
rebuild_response_v273 = copy.deepcopy(rebuild_response_v271)
rebuild_response_v273['properties']['server']['properties'].update(
{
'locked_reason': {
'type': ['null', 'string'],
},
},
)
rebuild_response_v273['properties']['server']['required'].append(
'locked_reason'
)
rebuild_response_v275 = copy.deepcopy(rebuild_response_v273)
rebuild_response_v275['properties']['server']['properties'].update(
{
'config_drive': {
# TODO(stephenfin): Our tests return null but this shouldn't happen
# in practice, apparently?
'type': ['string', 'boolean', 'null'],
},
'OS-EXT-AZ:availability_zone': {
'type': 'string',
},
'OS-EXT-SRV-ATTR:host': {
'type': ['string', 'null'],
},
'OS-EXT-SRV-ATTR:hypervisor_hostname': {
'type': ['string', 'null'],
},
'OS-EXT-SRV-ATTR:instance_name': {
'type': 'string',
},
'OS-EXT-STS:power_state': {
'type': 'integer',
'enum': [0, 1, 3, 4, 6, 7],
},
'OS-EXT-STS:task_state': {
'type': ['null', 'string'],
},
'OS-EXT-STS:vm_state': {
'type': 'string',
},
'OS-EXT-SRV-ATTR:hostname': {
'type': 'string',
},
'OS-EXT-SRV-ATTR:reservation_id': {
'type': ['string', 'null'],
},
'OS-EXT-SRV-ATTR:launch_index': {
'type': 'integer',
},
'OS-EXT-SRV-ATTR:kernel_id': {
'type': ['string', 'null'],
},
'OS-EXT-SRV-ATTR:ramdisk_id': {
'type': ['string', 'null'],
},
'OS-EXT-SRV-ATTR:root_device_name': {
'type': ['string', 'null'],
},
'os-extended-volumes:volumes_attached': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'id': {
'type': 'string',
},
'delete_on_termination': {
'type': 'boolean',
'default': False,
},
},
'required': ['id', 'delete_on_termination'],
'additionalProperties': False,
},
},
'OS-SRV-USG:launched_at': {
'oneOf': [
{'type': 'null'},
{'type': 'string', 'format': 'date-time'},
],
},
'OS-SRV-USG:terminated_at': {
'oneOf': [
{'type': 'null'},
{'type': 'string', 'format': 'date-time'},
],
},
'security_groups': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string',
},
},
'required': ['name'],
'additionalProperties': False,
},
},
'host_status': {
'type': 'string',
},
},
)
rebuild_response_v275['properties']['server']['required'].extend([
'config_drive',
'OS-EXT-AZ:availability_zone',
'OS-EXT-STS:power_state',
'OS-EXT-STS:task_state',
'OS-EXT-STS:vm_state',
'os-extended-volumes:volumes_attached',
'OS-SRV-USG:launched_at',
'OS-SRV-USG:terminated_at',
])
rebuild_response_v275['properties']['server']['properties']['addresses'][
'patternProperties'
]['^.+$']['items']['properties'].update({
'OS-EXT-IPS-MAC:mac_addr': {'type': 'string', 'format': 'mac-address'},
'OS-EXT-IPS:type': {'type': 'string', 'enum': ['fixed', 'floating']},
})
rebuild_response_v275['properties']['server']['properties']['addresses'][
'patternProperties'
]['^.+$']['items']['required'].extend([
'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type'
])
rebuild_response_v296 = copy.deepcopy(rebuild_response_v275)
rebuild_response_v296['properties']['server']['properties'].update({
'pinned_availability_zone': {
'type': ['null', 'string'],
},
})
rebuild_response_v296['properties']['server']['required'].append(
'pinned_availability_zone'
)

View File

@ -1163,6 +1163,29 @@ class ServersController(wsgi.Controller):
@validation.schema(schema.rebuild_v263, '2.63', '2.89')
@validation.schema(schema.rebuild_v290, '2.90', '2.93')
@validation.schema(schema.rebuild_v294, '2.94')
@validation.response_body_schema(schema.rebuild_response, '2.0', '2.8')
@validation.response_body_schema(
schema.rebuild_response_v29, '2.9', '2.18')
@validation.response_body_schema(
schema.rebuild_response_v219, '2.19', '2.25')
@validation.response_body_schema(
schema.rebuild_response_v226, '2.26', '2.45')
@validation.response_body_schema(
schema.rebuild_response_v246, '2.46', '2.53')
@validation.response_body_schema(
schema.rebuild_response_v254, '2.54', '2.56')
@validation.response_body_schema(
schema.rebuild_response_v257, '2.57', '2.62')
@validation.response_body_schema(
schema.rebuild_response_v263, '2.63', '2.70')
@validation.response_body_schema(
schema.rebuild_response_v271, '2.71', '2.72')
@validation.response_body_schema(
schema.rebuild_response_v273, '2.73', '2.74')
@validation.response_body_schema(
schema.rebuild_response_v275, '2.75', '2.95')
@validation.response_body_schema(
schema.rebuild_response_v296, '2.96')
def _action_rebuild(self, req, id, body):
"""Rebuild an instance with the given attributes."""
rebuild_dict = body['rebuild']
@ -1333,6 +1356,9 @@ class ServersController(wsgi.Controller):
@wsgi.action('createImage')
@validation.schema(schema.create_image, '2.0', '2.0')
@validation.schema(schema.create_image, '2.1')
@validation.response_body_schema(
schema.create_image_response, '2.0', '2.44')
@validation.response_body_schema(schema.create_image_response_v245, '2.45')
def _action_create_image(self, req, id, body):
"""Snapshot a server instance."""
context = req.environ['nova.context']

View File

@ -2,7 +2,7 @@
"flavor_access": [
{
"flavor_id": "%(flavor_id)s",
"tenant_id": "fake_tenant"
"tenant_id": "%(tenant_id)s"
}
]
}

View File

@ -11,7 +11,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from nova.tests.functional.api_sample_tests import api_sample_base
@ -21,7 +20,7 @@ class FlavorAccessTestsBase(api_sample_base.ApiSampleTestBaseV21):
def _add_tenant(self):
subs = {
'tenant_id': 'fake_tenant',
'tenant_id': self.api.project_id,
'flavor_id': '10',
}
response = self._do_post('flavors/10/action',
@ -49,7 +48,7 @@ class FlavorAccessSampleJsonTests(FlavorAccessTestsBase):
response = self._do_get('flavors/%s/os-flavor-access' % flavor_id)
subs = {
'flavor_id': flavor_id,
'tenant_id': 'fake_tenant',
'tenant_id': self.api.project_id,
}
self._verify_response('flavor-access-list-resp', subs, response, 200)
@ -61,7 +60,7 @@ class FlavorAccessSampleJsonTests(FlavorAccessTestsBase):
self._create_flavor()
self._add_tenant()
subs = {
'tenant_id': 'fake_tenant',
'tenant_id': self.api.project_id,
}
response = self._do_post('flavors/10/action',
"flavor-access-remove-tenant-req",
@ -88,7 +87,7 @@ class FlavorAccessV27SampleJsonTests(FlavorAccessTestsBase):
subs = {
'flavor_id': '10',
'tenant_id': 'fake_tenant'
'tenant_id': self.api.project_id
}
# Version 2.7+ will return HTTPConflict (409)
# if the flavor is public

View File

@ -75,7 +75,7 @@ class TestFlavorNotificationSample(
body = {
"addTenantAccess": {
"tenant": "fake_tenant"
"tenant": "6f70656e737461636b20342065766572"
}
}
self.admin_api.api_post(

View File

@ -15,12 +15,12 @@
from unittest import mock
from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import timeutils
import webob
from nova.api.openstack import common
from nova.api.openstack.compute import create_backup \
as create_backup_v21
from nova.api.openstack.compute import create_backup
from nova.compute import api
from nova.compute import utils as compute_utils
from nova import exception
@ -32,7 +32,7 @@ from nova.tests.unit import fake_instance
class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
test.NoDBTestCase):
create_backup = create_backup_v21
create_backup = create_backup
controller_name = 'CreateBackupController'
validation_error = exception.ValidationError
@ -54,7 +54,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
},
}
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
image = dict(id=uuids.image_id, status='ACTIVE', name='Backup 1',
properties=metadata)
instance = fake_instance.fake_instance_obj(self.context)
@ -70,7 +70,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
extra_properties=metadata)
self.assertEqual(202, res.status_int)
self.assertIn('fake-image-id', res.headers['Location'])
self.assertIn(uuids.image_id, res.headers['Location'])
def test_create_backup_no_name(self):
# Name is required for backups.
@ -107,7 +107,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
'rotation': 1,
},
}
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
image = dict(id=uuids.image_id, status='ACTIVE', name='Backup 1',
properties={})
instance = fake_instance.fake_instance_obj(self.context)
self.mock_get.return_value = instance
@ -217,7 +217,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
},
}
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
image = dict(id=uuids.image_id, status='ACTIVE', name='Backup 1',
properties={})
instance = fake_instance.fake_instance_obj(self.context)
self.mock_get.return_value = instance
@ -246,7 +246,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
},
}
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
image = dict(id=uuids.image_id, status='ACTIVE', name='Backup 1',
properties={})
instance = fake_instance.fake_instance_obj(self.context)
self.mock_get.return_value = instance
@ -261,7 +261,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
extra_properties={})
self.assertEqual(202, res.status_int)
self.assertIn('fake-image-id', res.headers['Location'])
self.assertIn(uuids.image_id, res.headers['Location'])
@mock.patch.object(common, 'check_img_metadata_properties_quota')
@mock.patch.object(api.API, 'backup')
@ -275,7 +275,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
},
}
image = dict(id='fake-image-id', status='ACTIVE', name='Backup 1',
image = dict(id=uuids.image_id, status='ACTIVE', name='Backup 1',
properties={})
instance = fake_instance.fake_instance_obj(self.context)
self.mock_get.return_value = instance
@ -289,11 +289,11 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
'daily', 1,
extra_properties={})
self.assertEqual(202, res.status_int)
self.assertIn('fake-image-id', res.headers['Location'])
self.assertIn(uuids.image_id, res.headers['Location'])
@mock.patch.object(common, 'check_img_metadata_properties_quota')
@mock.patch.object(api.API, 'backup', return_value=dict(
id='fake-image-id', status='ACTIVE', name='Backup 1', properties={}))
id=uuids.image_id, status='ACTIVE', name='Backup 1', properties={}))
def test_create_backup_v2_45(self, mock_backup, mock_check_image):
"""Tests the 2.45 microversion to ensure the Location header is not
in the response.
@ -310,7 +310,7 @@ class CreateBackupTestsV21(admin_only_action_common.CommonMixin,
req = fakes.HTTPRequest.blank('', version='2.45')
res = self.controller._create_backup(req, instance['uuid'], body=body)
self.assertIsInstance(res, dict)
self.assertEqual('fake-image-id', res['image_id'])
self.assertEqual(uuids.image_id, res['image_id'])
@mock.patch.object(common, 'check_img_metadata_properties_quota')
@mock.patch.object(api.API, 'backup')
@ -396,7 +396,7 @@ class CreateBackupTestsV239(test.NoDBTestCase):
def setUp(self):
super(CreateBackupTestsV239, self).setUp()
self.controller = create_backup_v21.CreateBackupController()
self.controller = create_backup.CreateBackupController()
self.req = fakes.HTTPRequest.blank('', version='2.39')
@mock.patch.object(common, 'check_img_metadata_properties_quota')

View File

@ -16,11 +16,11 @@
import datetime
from unittest import mock
from oslo_utils.fixture import uuidsentinel as uuids
from webob import exc
from nova.api.openstack import api_version_request as api_version
from nova.api.openstack.compute import flavor_access \
as flavor_access_v21
from nova.api.openstack.compute import flavor_access
from nova.api.openstack.compute import flavors as flavors_api
from nova import context
from nova import exception
@ -57,9 +57,9 @@ FLAVORS = {
ACCESS_LIST = [
{'flavor_id': '2', 'project_id': 'proj2'},
{'flavor_id': '2', 'project_id': 'proj3'},
{'flavor_id': '3', 'project_id': 'proj3'},
{'flavor_id': '2', 'project_id': uuids.proj2},
{'flavor_id': '2', 'project_id': uuids.proj3},
{'flavor_id': '3', 'project_id': uuids.proj3},
]
@ -126,8 +126,8 @@ def fake_get_flavor_projects_from_db(context, flavorid):
class FlavorAccessTestV21(test.NoDBTestCase):
api_version = "2.1"
FlavorAccessController = flavor_access_v21.FlavorAccessController
FlavorActionController = flavor_access_v21.FlavorActionController
FlavorAccessController = flavor_access.FlavorAccessController
FlavorActionController = flavor_access.FlavorActionController
_prefix = "/v2/%s" % fakes.FAKE_PROJECT_ID
validation_ex = exception.ValidationError
@ -175,8 +175,8 @@ class FlavorAccessTestV21(test.NoDBTestCase):
req.environ = {"nova.context": context.RequestContext(
'fake_user', fakes.FAKE_PROJECT_ID)}
expected = {'flavor_access': [
{'flavor_id': '2', 'tenant_id': 'proj2'},
{'flavor_id': '2', 'tenant_id': 'proj3'}]}
{'flavor_id': '2', 'tenant_id': uuids.proj2},
{'flavor_id': '2', 'tenant_id': uuids.proj3}]}
result = self.flavor_access_controller.index(req, '2')
self.assertEqual(result, expected)
@ -192,7 +192,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
expected = {'flavors': [{'id': '0'}, {'id': '1'}, {'id': '2'}]}
req = fakes.HTTPRequest.blank(self._prefix + '/flavors',
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj2'
req.environ['nova.context'].project_id = uuids.proj2
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
@ -217,7 +217,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
url = self._prefix + '/flavors?is_public=false'
req = fakes.HTTPRequest.blank(url,
use_admin_context=True)
req.environ['nova.context'].project_id = 'proj2'
req.environ['nova.context'].project_id = uuids.proj2
result = self.flavor_controller.index(req)
self._verify_flavor_list(result['flavors'], expected['flavors'])
@ -264,12 +264,13 @@ class FlavorAccessTestV21(test.NoDBTestCase):
def test_add_tenant_access(self):
def stub_add_flavor_access(context, flavor_id, projectid):
self.assertEqual(3, flavor_id, "flavor_id")
self.assertEqual("proj2", projectid, "projectid")
self.assertEqual(uuids.proj2, projectid, "projectid")
self.stub_out('nova.objects.Flavor._flavor_add_project',
stub_add_flavor_access)
expected = {'flavor_access':
[{'flavor_id': '3', 'tenant_id': 'proj3'}]}
body = {'addTenantAccess': {'tenant': 'proj2'}}
expected = {
'flavor_access': [{'flavor_id': '3', 'tenant_id': uuids.proj3}]
}
body = {'addTenantAccess': {'tenant': uuids.proj2}}
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
@ -280,7 +281,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
@mock.patch('nova.objects.Flavor.get_by_flavor_id',
side_effect=exception.FlavorNotFound(flavor_id='1'))
def test_add_tenant_access_with_flavor_not_found(self, mock_get):
body = {'addTenantAccess': {'tenant': 'proj2'}}
body = {'addTenantAccess': {'tenant': uuids.proj2}}
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
self.assertRaises(exc.HTTPNotFound,
@ -290,7 +291,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
def test_add_tenant_access_with_no_tenant(self):
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'addTenantAccess': {'foo': 'proj2'}}
body = {'addTenantAccess': {'foo': uuids.proj2}}
self.assertRaises(self.validation_ex,
self.flavor_action_controller._add_tenant_access,
req, '2', body=body)
@ -309,7 +310,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
self._prefix + '/flavors/3/os-flavor-access')
req.environ = {"nova.context": context.RequestContext(
'fake_user', fakes.FAKE_PROJECT_ID)}
body = {'addTenantAccess': {'tenant': 'proj2'}}
body = {'addTenantAccess': {'tenant': uuids.proj2}}
self.assertRaises(exc.HTTPConflict,
self.flavor_action_controller._add_tenant_access,
req, '3', body=body)
@ -320,7 +321,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
project_id=projectid)
self.stub_out('nova.objects.Flavor._flavor_del_project',
stub_remove_flavor_access)
body = {'removeTenantAccess': {'tenant': 'proj2'}}
body = {'removeTenantAccess': {'tenant': uuids.proj2}}
req = fakes.HTTPRequest.blank(
self._prefix + '/flavors/3/os-flavor-access')
req.environ = {"nova.context": context.RequestContext(
@ -330,7 +331,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
req, '3', body=body)
def test_add_tenant_access_is_public(self):
body = {'addTenantAccess': {'tenant': 'proj2'}}
body = {'addTenantAccess': {'tenant': uuids.proj2}}
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
req.api_version_request = api_version.APIVersionRequest('2.7')
@ -343,7 +344,7 @@ class FlavorAccessTestV21(test.NoDBTestCase):
def test_delete_tenant_access_with_no_tenant(self, mock_api_get):
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'removeTenantAccess': {'foo': 'proj2'}}
body = {'removeTenantAccess': {'foo': uuids.proj2}}
self.assertRaises(self.validation_ex,
self.flavor_action_controller._remove_tenant_access,
req, '2', body=body)
@ -359,30 +360,32 @@ class FlavorAccessTestV21(test.NoDBTestCase):
"""Tests the case that the tenant does not exist in Keystone."""
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'addTenantAccess': {'tenant': 'proj2'}}
body = {'addTenantAccess': {'tenant': uuids.proj2}}
self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._add_tenant_access,
req, '2', body=body)
mock_verify.assert_called_once_with(
req.environ['nova.context'], 'proj2')
req.environ['nova.context'], uuids.proj2)
@mock.patch('nova.objects.Flavor.remove_access')
@mock.patch('nova.api.openstack.identity.verify_project_id',
side_effect=exc.HTTPBadRequest(
explanation="Project ID proj2 is not a valid project."))
@mock.patch('nova.api.openstack.identity.verify_project_id')
def test_remove_tenant_access_with_invalid_tenant(self,
mock_verify,
mock_remove_access):
"""Tests the case that the tenant does not exist in Keystone."""
mock_verify.side_effect = exc.HTTPBadRequest(explanation=(
f"Project ID {uuids.proj2} is not a valid project."
))
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'removeTenantAccess': {'tenant': 'proj2'}}
body = {'removeTenantAccess': {'tenant': uuids.proj2}}
self.flavor_action_controller._remove_tenant_access(
req, '2', body=body)
mock_verify.assert_called_once_with(
req.environ['nova.context'], 'proj2')
mock_remove_access.assert_called_once_with('proj2')
req.environ['nova.context'], uuids.proj2)
mock_remove_access.assert_called_once_with(uuids.proj2)
@mock.patch('nova.api.openstack.identity.verify_project_id',
side_effect=exc.HTTPBadRequest(
@ -395,10 +398,10 @@ class FlavorAccessTestV21(test.NoDBTestCase):
"""
req = fakes.HTTPRequest.blank(self._prefix + '/flavors/2/action',
use_admin_context=True)
body = {'removeTenantAccess': {'tenant': 'proj2'}}
body = {'removeTenantAccess': {'tenant': uuids.proj2}}
self.assertRaises(exc.HTTPBadRequest,
self.flavor_action_controller._remove_tenant_access,
req, '2', body=body)
mock_verify.assert_called_once_with(
req.environ['nova.context'], 'proj2')
req.environ['nova.context'], uuids.proj2)

View File

@ -395,7 +395,7 @@ class KeypairsTestV210(KeypairsTestV22):
with mock.patch.object(self.controller.api, 'get_key_pairs') as mock_g:
self.controller.index(req)
userid = mock_g.call_args_list[0][0][1]
self.assertEqual('fake_user', userid)
self.assertEqual(fakes.FAKE_USER_ID, userid)
class KeypairsTestV235(test.TestCase):
@ -421,7 +421,7 @@ class KeypairsTestV235(test.TestCase):
res_dict = self.controller.index(req)
mock_kp_get.assert_called_once_with(
req.environ['nova.context'], 'fake_user',
req.environ['nova.context'], fakes.FAKE_USER_ID,
limit=3, marker='fake_marker')
response = {'keypairs': [{'keypair': dict(keypair_data, name='FAKE',
type='ssh')}]}
@ -458,7 +458,7 @@ class KeypairsTestV235(test.TestCase):
self.controller.index(req)
mock_kp_get.assert_called_once_with(
req.environ['nova.context'], 'fake_user',
req.environ['nova.context'], fakes.FAKE_USER_ID,
limit=None, marker=None)

View File

@ -1002,12 +1002,15 @@ class ServerActionsControllerTestV21(test.TestCase):
response = self.controller._action_create_image(self.req, FAKE_UUID,
body=body)
location = response.headers['Location']
self.assertEqual(self.image_url + '123' if self.image_url else
self.image_api.generate_image_url('123', self.context),
location)
if self.image_url:
expected_location = self.image_url + uuids.snapshot_id
else:
expected_location = self.image_api.generate_image_url(
uuids.snapshot_id, self.context
)
self.assertEqual(response.headers['Location'], expected_location)
def test_create_image_v2_45(self):
def test_create_image_v245(self):
"""Tests the createImage server action API with the 2.45 microversion
where there is a response body but no Location header.
"""
@ -1020,7 +1023,7 @@ class ServerActionsControllerTestV21(test.TestCase):
response = self.controller._action_create_image(req, FAKE_UUID,
body=body)
self.assertIsInstance(response, dict)
self.assertEqual('123', response['image_id'])
self.assertEqual(uuids.snapshot_id, response['image_id'])
def test_create_image_name_too_long(self):
long_name = 'a' * 260
@ -1254,9 +1257,13 @@ class ServerActionsControllerTestV21(test.TestCase):
response = self.controller._action_create_image(self.req, FAKE_UUID,
body=body)
location = response.headers['Location']
self.assertEqual(self.image_url + '123' if self.image_url else
self.image_api.generate_image_url('123', self.context), location)
if self.image_url:
expected_location = self.image_url + uuids.snapshot_id
else:
expected_location = self.image_api.generate_image_url(
uuids.snapshot_id, self.context
)
self.assertEqual(response.headers['Location'], expected_location)
def test_create_image_with_too_much_metadata(self):
body = {

View File

@ -171,7 +171,7 @@ def fake_get_inst_mappings_by_instance_uuids_from_db(*args, **kwargs):
'transport_url': 'fake://nowhere/', 'updated_at': None,
'database_connection': uuids.cell1, 'created_at': None,
'disabled': False},
'project_id': 'fake-project'
'project_id': fakes.FAKE_PROJECT_ID,
}]
@ -265,7 +265,7 @@ class _ServersControllerTest(ControllerTest):
return {
"server": {
"id": uuid,
"user_id": "fake_user",
"user_id": fakes.FAKE_USER_ID,
"created": "2010-10-10T12:00:00Z",
"updated": "2010-11-11T11:00:00Z",
"progress": progress,
@ -3767,6 +3767,21 @@ class ServersControllerRebuildTestV275(ControllerTest):
microversion = '2.75'
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
def setUp(self):
super().setUp()
mock_rebuild = mock.patch(
'nova.compute.api.API.rebuild', return_value=None)
self.mock_rebuild = mock_rebuild.start()
self.addCleanup(mock_rebuild.stop)
self.mock_get_instance_host_status = self.useFixture(
fixtures.MockPatchObject(
compute_api.API, 'get_instance_host_status',
return_value='UP'
)
).mock
def test_rebuild_response_no_show_server_only_attributes_old_version(self):
# There are some old server attributes which were added only for
# GET server APIs not for Rebuild. GET server and Rebuild server share
@ -3799,11 +3814,29 @@ class ServersControllerRebuildTestV275(ControllerTest):
req = fakes.HTTPRequest.blank(self.path_with_query % 'unknown=1',
use_admin_context=True,
version=self.microversion)
fake_get = fakes.fake_compute_get(
self.mock_get.side_effect = fakes.fake_compute_get(
id=2,
display_description="",
uuid=FAKE_UUID,
node="node-fake",
reservation_id="r-1",
launch_index=0,
kernel_id=UUID1,
ramdisk_id=UUID2,
display_name="server2",
host='host',
root_device_name="/dev/vda",
user_data="userdata",
metadata={"seq": "2"},
availability_zone='nova',
launched_at=None,
terminated_at=None,
task_state="ACTIVE",
vm_state=vm_states.ACTIVE,
power_state=1,
project_id=req.environ['nova.context'].project_id,
user_id=req.environ['nova.context'].user_id)
self.mock_get.side_effect = fake_get
res_dict = self.controller._action_rebuild(req, FAKE_UUID,
body=body).obj
for field in GET_ONLY_FIELDS:
@ -3829,6 +3862,13 @@ class ServersControllerRebuildTestV290(ControllerTest):
self.mock_rebuild = mock_rebuild.start()
self.addCleanup(mock_rebuild.stop)
self.mock_get_instance_host_status = self.useFixture(
fixtures.MockPatchObject(
compute_api.API, 'get_instance_host_status',
return_value='UP'
)
).mock
def _get_request(self, body=None):
req = fakes.HTTPRequest.blank(
self.path_action % FAKE_UUID,
@ -3851,6 +3891,29 @@ class ServersControllerRebuildTestV290(ControllerTest):
}
req = self._get_request(body)
self.mock_get.side_effect = fakes.fake_compute_get(
id=2,
display_description="",
uuid=FAKE_UUID,
node="node-fake",
reservation_id="r-1",
launch_index=0,
kernel_id=UUID1,
ramdisk_id=UUID2,
display_name="server2",
host='host',
root_device_name="/dev/vda",
user_data="userdata",
metadata={"seq": "2"},
availability_zone='nova',
launched_at=None,
terminated_at=None,
task_state="ACTIVE",
vm_state=vm_states.ACTIVE,
power_state=1,
project_id=req.environ['nova.context'].project_id,
user_id=req.environ['nova.context'].user_id)
# There's nothing to check here from the return value since the
# 'rebuild' API is a cast and we immediately fetch the instance from
# the database after this cast...which returns a mocked Instance
@ -5826,7 +5889,7 @@ class ServersControllerCreateTest(_ServersControllerCreateTest):
mock_count.return_value = count
mock_get_all_p.return_value = {'project_id': fakes.FAKE_PROJECT_ID}
mock_get_all_pu.return_value = {'project_id': fakes.FAKE_PROJECT_ID,
'user_id': 'fake_user'}
'user_id': fakes.FAKE_USER_ID}
if resource in db.PER_PROJECT_QUOTAS:
mock_get_all_p.return_value[resource] = quota
else:
@ -7235,7 +7298,7 @@ class ServersControllerCreateTestV274(_ServersControllerCreateTest):
def setUp(self):
super(ServersControllerCreateTestV274, self).setUp()
self.req.environ['nova.context'] = fakes.FakeRequestContext(
user_id='fake_user',
user_id=fakes.FAKE_USER_ID,
project_id=self.project_id,
is_admin=True)
self.mock_get = self.useFixture(
@ -7533,8 +7596,8 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
expected_server = {
"server": {
"id": self.uuid,
"user_id": "fake_user",
"tenant_id": "fake_project",
"user_id": fakes.FAKE_USER_ID,
"tenant_id": fakes.FAKE_PROJECT_ID,
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@ -7552,12 +7615,12 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
},
"flavor": {
"id": "1",
"links": [
{
"rel": "bookmark",
"href": flavor_bookmark,
},
],
"links": [
{
"rel": "bookmark",
"href": flavor_bookmark,
},
],
},
"addresses": {
'test1': [
@ -7623,8 +7686,8 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
expected_server = {
"server": {
"id": self.uuid,
"user_id": "fake_user",
"tenant_id": "fake_project",
"user_id": fakes.FAKE_USER_ID,
"tenant_id": fakes.FAKE_PROJECT_ID,
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"name": "test_server",
@ -7641,12 +7704,12 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
},
"flavor": {
"id": "1",
"links": [
{
"rel": "bookmark",
"href": flavor_bookmark,
},
],
"links": [
{
"rel": "bookmark",
"href": flavor_bookmark,
},
],
},
"addresses": {
'test1': [
@ -7822,8 +7885,8 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
expected_server = {
"server": {
"id": self.uuid,
"user_id": "fake_user",
"tenant_id": "fake_project",
"user_id": fakes.FAKE_USER_ID,
"tenant_id": fakes.FAKE_PROJECT_ID,
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 100,
@ -7841,12 +7904,12 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
},
"flavor": {
"id": "1",
"links": [
{
"rel": "bookmark",
"href": flavor_bookmark,
},
],
"links": [
{
"rel": "bookmark",
"href": flavor_bookmark,
},
],
},
"addresses": {
'test1': [
@ -7914,8 +7977,8 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
expected_server = {
"server": {
"id": self.uuid,
"user_id": "fake_user",
"tenant_id": "fake_project",
"user_id": fakes.FAKE_USER_ID,
"tenant_id": fakes.FAKE_PROJECT_ID,
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@ -7934,7 +7997,7 @@ class ServersViewBuilderTest(_ServersViewBuilderTest):
"flavor": {
"id": "1",
"links": [
{
{
"rel": "bookmark",
"href": flavor_bookmark,
},
@ -8042,8 +8105,8 @@ class ServersViewBuilderTestV269(_ServersViewBuilderTest):
expected = {
"servers": [{
"id": self.uuid,
"user_id": "fake_user",
"tenant_id": "fake_project",
"user_id": fakes.FAKE_USER_ID,
"tenant_id": fakes.FAKE_PROJECT_ID,
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@ -8225,8 +8288,8 @@ class ServersViewBuilderTestV269(_ServersViewBuilderTest):
expected = {
"server": {
"id": self.uuid,
"user_id": "fake_user",
"tenant_id": "fake_project",
"user_id": fakes.FAKE_USER_ID,
"tenant_id": fakes.FAKE_PROJECT_ID,
"created": '1955-11-05T00:00:00Z',
"status": "UNKNOWN",
"image": {
@ -8288,7 +8351,7 @@ class ServersViewBuilderTestV269(_ServersViewBuilderTest):
"server": {
"id": self.uuid,
"user_id": "UNKNOWN",
"tenant_id": "fake_project",
"tenant_id": fakes.FAKE_PROJECT_ID,
"created": '1955-11-05T00:00:00Z',
"status": "UNKNOWN",
"image": "",

View File

@ -16,6 +16,7 @@
import datetime
from oslo_serialization import jsonutils
from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import timeutils
from oslo_utils import uuidutils
import routes
@ -139,8 +140,10 @@ def stub_out_compute_api_snapshot(test):
# emulate glance rejecting image names which are too long
if len(name) > 256:
raise exc.Invalid
return dict(id='123', status='ACTIVE', name=name,
properties=extra_properties)
return {
'id': uuids.snapshot_id, 'status': 'ACTIVE', 'name': name,
'properties': extra_properties,
}
test.stub_out('nova.compute.api.API.snapshot', snapshot)
@ -154,10 +157,12 @@ class stub_out_compute_api_backup(object):
def backup(self, context, instance, name, backup_type, rotation,
extra_properties=None):
self.extra_props_last_call = extra_properties
props = dict(backup_type=backup_type,
rotation=rotation)
props = {'backup_type': backup_type, 'rotation': rotation}
props.update(extra_properties or {})
return dict(id='123', status='ACTIVE', name=name, properties=props)
return {
'id': uuids.backup_id, 'status': 'ACTIVE', 'name': name,
'properties': props,
}
def stub_out_nw_api(test, cls=None, private=None, publics=None):
@ -244,11 +249,12 @@ class HTTPRequest(os_wsgi.Request):
if use_admin_context:
roles.append('admin')
project_id = kwargs.pop('project_id', FAKE_PROJECT_ID)
user_id = kwargs.pop('user_id', FAKE_USER_ID)
version = kwargs.pop('version', os_wsgi.DEFAULT_API_VERSION)
defaults.update(kwargs)
out = super(HTTPRequest, cls).blank(*args, **defaults)
out.environ['nova.context'] = FakeRequestContext(
user_id='fake_user',
user_id=user_id,
project_id=project_id,
is_admin=use_admin_context,
roles=roles)
@ -434,9 +440,9 @@ def stub_instance(id=1, user_id=None, project_id=None, host=None,
services=None, trusted_certs=None, hidden=False,
compute_id=None):
if user_id is None:
user_id = 'fake_user'
user_id = FAKE_USER_ID
if project_id is None:
project_id = 'fake_project'
project_id = FAKE_PROJECT_ID
if metadata:
metadata = [{'key': k, 'value': v} for k, v in metadata.items()]

View File

@ -20,6 +20,7 @@ from oslo_utils import timeutils
from nova.api.openstack.compute import migrate_server
from nova.api.openstack.compute import servers
from nova.compute import api as compute
from nova.compute import power_state
from nova.compute import vm_states
import nova.conf
from nova import exception
@ -63,14 +64,18 @@ class ServersPolicyTest(base.BasePolicyTest):
self.controller._view_builder._add_security_grps = mock.MagicMock()
self.controller._view_builder._get_metadata = mock.MagicMock()
self.controller._view_builder._get_addresses = mock.MagicMock()
self.controller._view_builder._get_host_id = mock.MagicMock()
self.controller._view_builder._get_host_id = mock.MagicMock(
return_value=''
)
self.controller._view_builder._get_fault = mock.MagicMock()
self.instance = fake_instance.fake_instance_obj(
self.project_member_context,
id=1, uuid=uuids.fake_id, project_id=self.project_id,
user_id=user_id, vm_state=vm_states.ACTIVE,
system_metadata={}, expected_attrs=['system_metadata'])
self.project_member_context,
id=1, uuid=uuids.fake_id, project_id=self.project_id,
user_id=user_id, vm_state=vm_states.ACTIVE,
system_metadata={}, expected_attrs=['system_metadata'],
task_state=None, power_state=power_state.SHUTDOWN,
hostname='foo', launch_index=0)
self.mock_flavor = self.useFixture(
fixtures.MockPatch('nova.compute.flavors.get_flavor_by_flavor_id'
@ -912,7 +917,8 @@ class ServersPolicyTest(base.BasePolicyTest):
self.assertNotIn(attr, resp['server'])
@mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid')
@mock.patch('nova.compute.api.API.get_instance_host_status')
@mock.patch('nova.compute.api.API.get_instance_host_status',
return_value=fields.HostStatus.UP)
@mock.patch('nova.compute.api.API.rebuild')
def test_server_rebuild_with_extended_attr_policy(self, mock_rebuild,
mock_get, mock_bdm):
@ -1011,7 +1017,8 @@ class ServersPolicyTest(base.BasePolicyTest):
self.assertNotIn('host_status', resp['server'])
@mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid')
@mock.patch('nova.compute.api.API.get_instance_host_status')
@mock.patch('nova.compute.api.API.get_instance_host_status',
return_value=fields.HostStatus.UP)
@mock.patch('nova.compute.api.API.rebuild')
def test_server_rebuild_with_host_status_policy(self, mock_rebuild,
mock_status, mock_bdm):