Merge "api: Add response body schemas for remaining server action APIs"

This commit is contained in:
Zuul
2024-11-26 20:24:35 +00:00
committed by Gerrit Code Review
25 changed files with 900 additions and 174 deletions
@@ -2,7 +2,7 @@
"flavor_access": [
{
"flavor_id": "10",
"tenant_id": "fake_tenant"
"tenant_id": "6f70656e737461636b20342065766572"
}
]
}
@@ -2,7 +2,7 @@
"flavor_access": [
{
"flavor_id": "10",
"tenant_id": "fake_tenant"
"tenant_id": "6f70656e737461636b20342065766572"
}
]
}
+1 -1
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,
+3 -2
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']
+9 -3
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.
+7 -3
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.
@@ -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(
+10 -7
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)
@@ -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,
}
@@ -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,
}
+21 -4
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',
}
@@ -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)
@@ -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,
}
+491 -32
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'
)
+26
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']
@@ -2,7 +2,7 @@
"flavor_access": [
{
"flavor_id": "%(flavor_id)s",
"tenant_id": "fake_tenant"
"tenant_id": "%(tenant_id)s"
}
]
}
@@ -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
@@ -75,7 +75,7 @@ class TestFlavorNotificationSample(
body = {
"addTenantAccess": {
"tenant": "fake_tenant"
"tenant": "6f70656e737461636b20342065766572"
}
}
self.admin_api.api_post(
@@ -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')
@@ -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)
@@ -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)
@@ -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 = {
@@ -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": "",
+14 -8
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()]
+14 -7
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):