Coverage for nova/api/openstack/compute/schemas/servers.py: 100%
149 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-24 11:16 +0000
1# Copyright 2014 NEC Corporation. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
15import copy
17from nova.api.validation import parameter_types
18from nova.api.validation.parameter_types import multi_params
19from nova.objects import instance
21_legacy_block_device_mapping = {
22 'type': 'object',
23 'properties': {
24 'virtual_name': {
25 'type': 'string', 'maxLength': 255,
26 },
27 'volume_id': parameter_types.volume_id,
28 'snapshot_id': parameter_types.image_id,
29 'volume_size': parameter_types.volume_size,
30 # Do not allow empty device names and number values and
31 # containing spaces(defined in nova/block_device.py:from_api())
32 'device_name': {
33 'type': 'string', 'minLength': 1, 'maxLength': 255,
34 'pattern': '^[a-zA-Z0-9._-r/]*$',
35 },
36 # Defined as boolean in nova/block_device.py:from_api()
37 'delete_on_termination': parameter_types.boolean,
38 'no_device': {},
39 # Defined as mediumtext in column "connection_info" in table
40 # "block_device_mapping"
41 'connection_info': {
42 'type': 'string', 'maxLength': 16777215
43 },
44 },
45 'additionalProperties': False
46}
48_block_device_mapping_v2_new_item = {
49 # defined in nova/block_device.py:from_api()
50 # NOTE: Client can specify the Id with the combination of
51 # source_type and uuid, or a single attribute like volume_id/
52 # image_id/snapshot_id.
53 'source_type': {
54 'type': 'string',
55 'enum': ['volume', 'image', 'snapshot', 'blank'],
56 },
57 'uuid': {
58 'type': 'string', 'minLength': 1, 'maxLength': 255,
59 'pattern': '^[a-zA-Z0-9._-]*$',
60 },
61 'image_id': parameter_types.image_id,
62 'destination_type': {
63 'type': 'string',
64 'enum': ['local', 'volume'],
65 },
66 # Defined as varchar(255) in column "guest_format" in table
67 # "block_device_mapping"
68 'guest_format': {
69 'type': 'string', 'maxLength': 255,
70 },
71 # Defined as varchar(255) in column "device_type" in table
72 # "block_device_mapping"
73 'device_type': {
74 'type': 'string', 'maxLength': 255,
75 },
76 # Defined as varchar(255) in column "disk_bus" in table
77 # "block_device_mapping"
78 'disk_bus': {
79 'type': 'string', 'maxLength': 255,
80 },
81 # Defined as integer in nova/block_device.py:from_api()
82 # NOTE(mriedem): boot_index=None is also accepted for backward
83 # compatibility with the legacy v2 API.
84 'boot_index': {
85 'type': ['integer', 'string', 'null'],
86 'pattern': '^-?[0-9]+$',
87 },
88}
90_block_device_mapping_v2 = copy.deepcopy(_legacy_block_device_mapping)
91_block_device_mapping_v2['properties'].update(
92 _block_device_mapping_v2_new_item
93)
95_hints = {
96 'type': 'object',
97 'properties': {
98 'group': {
99 'type': 'string',
100 'format': 'uuid'
101 },
102 'different_host': {
103 # NOTE: The value of 'different_host' is the set of server
104 # uuids where a new server is scheduled on a different host.
105 # A user can specify one server as string parameter and should
106 # specify multiple servers as array parameter instead.
107 'oneOf': [
108 {
109 'type': 'string',
110 'format': 'uuid'
111 },
112 {
113 'type': 'array',
114 'items': parameter_types.server_id
115 }
116 ]
117 },
118 'same_host': {
119 # NOTE: The value of 'same_host' is the set of server
120 # uuids where a new server is scheduled on the same host.
121 'type': ['string', 'array'],
122 'items': parameter_types.server_id
123 },
124 'query': {
125 # NOTE: The value of 'query' is converted to dict data with
126 # jsonutils.loads() and used for filtering hosts.
127 'type': ['string', 'object'],
128 },
129 # NOTE: The value of 'target_cell' is the cell name what cell
130 # a new server is scheduled on.
131 'target_cell': parameter_types.name,
132 'different_cell': {
133 'type': ['string', 'array'],
134 'items': {
135 'type': 'string'
136 }
137 },
138 'build_near_host_ip': parameter_types.ip_address,
139 'cidr': {
140 'type': 'string',
141 'pattern': '^/[0-9a-f.:]+$'
142 },
143 },
144 # NOTE: As this Mail:
145 # http://lists.openstack.org/pipermail/openstack-dev/2015-June/067996.html
146 # pointed out the limit the scheduler-hints in the API is problematic. So
147 # relax it.
148 'additionalProperties': True
149}
151create = {
152 'type': 'object',
153 'properties': {
154 'server': {
155 'type': 'object',
156 'properties': {
157 'name': parameter_types.name,
158 # NOTE(gmann): In case of boot from volume, imageRef was
159 # allowed as the empty string also So keeping the same
160 # behavior and allow empty string in case of boot from
161 # volume only. Python code make sure empty string is
162 # not allowed for other cases.
163 'imageRef': parameter_types.image_id_or_empty_string,
164 'flavorRef': parameter_types.flavor_ref,
165 'adminPass': parameter_types.admin_password,
166 'metadata': parameter_types.metadata,
167 'networks': {
168 'type': 'array',
169 'items': {
170 'type': 'object',
171 'properties': {
172 'fixed_ip': parameter_types.ip_address,
173 'port': {
174 'oneOf': [{'type': 'string', 'format': 'uuid'},
175 {'type': 'null'}]
176 },
177 'uuid': {'type': 'string'},
178 },
179 'additionalProperties': False,
180 }
181 },
182 'OS-DCF:diskConfig': parameter_types.disk_config,
183 'accessIPv4': parameter_types.accessIPv4,
184 'accessIPv6': parameter_types.accessIPv6,
185 'personality': parameter_types.personality,
186 'availability_zone': parameter_types.name,
187 'block_device_mapping': {
188 'type': 'array',
189 'items': _legacy_block_device_mapping
190 },
191 'block_device_mapping_v2': {
192 'type': 'array',
193 'items': _block_device_mapping_v2
194 },
195 'config_drive': parameter_types.boolean,
196 'key_name': parameter_types.name,
197 'min_count': parameter_types.positive_integer,
198 'max_count': parameter_types.positive_integer,
199 'return_reservation_id': parameter_types.boolean,
200 'security_groups': {
201 'type': 'array',
202 'items': {
203 'type': 'object',
204 'properties': {
205 # NOTE(oomichi): allocate_for_instance() of
206 # network/neutron.py gets security_group names
207 # or UUIDs from this parameter.
208 # parameter_types.name allows both format.
209 'name': parameter_types.name,
210 },
211 'additionalProperties': False,
212 }
213 },
214 'user_data': {
215 'type': 'string',
216 'format': 'base64',
217 'maxLength': 65535
218 }
219 },
220 'required': ['name', 'flavorRef'],
221 'additionalProperties': False,
222 },
223 'os:scheduler_hints': _hints,
224 'OS-SCH-HNT:scheduler_hints': _hints,
225 },
226 'required': ['server'],
227 'additionalProperties': False,
228}
230create_v20 = copy.deepcopy(create)
231create_v20['properties']['server'][
232 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces
233create_v20['properties']['server']['properties'][
234 'availability_zone'] = parameter_types.name_with_leading_trailing_spaces
235create_v20['properties']['server']['properties'][
236 'key_name'] = parameter_types.name_with_leading_trailing_spaces
237create_v20['properties']['server']['properties'][
238 'security_groups']['items']['properties']['name'] = (
239 parameter_types.name_with_leading_trailing_spaces)
240create_v20['properties']['server']['properties']['user_data'] = {
241 'oneOf': [
242 {'type': 'string', 'format': 'base64', 'maxLength': 65535},
243 {'type': 'null'},
244 ],
245}
247create_v219 = copy.deepcopy(create)
248create_v219['properties']['server'][
249 'properties']['description'] = parameter_types.description
251create_v232 = copy.deepcopy(create_v219)
252create_v232['properties']['server'][
253 'properties']['networks']['items'][
254 'properties']['tag'] = parameter_types.tag
255create_v232['properties']['server'][
256 'properties']['block_device_mapping_v2']['items'][
257 'properties']['tag'] = parameter_types.tag
259# NOTE(artom) the following conditional was merged as
260# "if version == '2.32'" The intent all along was to check whether
261# version was greater than or equal to 2.32. In other words, we wanted
262# to support tags in versions 2.32 and up, but ended up supporting them
263# in version 2.32 only. Since we need a new microversion to add request
264# body attributes, tags have been re-added in version 2.42.
266# NOTE(gmann) Below schema 'create_v233' is added (builds on 2.19 schema)
267# to keep the above mentioned behavior while merging the extension schema code
268# into server schema file. Below is the ref code where BDM tag was originally
269# got added for 2.32 microversion *only*.
270# Ref- https://opendev.org/openstack/nova/src/commit/
271# 9882a60e69a5ab8da314a199a56defc05098b743/nova/api/
272# openstack/compute/block_device_mapping.py#L71
273create_v233 = copy.deepcopy(create_v219)
274create_v233['properties']['server'][
275 'properties']['networks']['items'][
276 'properties']['tag'] = parameter_types.tag
278# 2.37 builds on 2.32 and makes the following changes:
279# 1. server.networks is required
280# 2. server.networks is now either an enum or a list
281# 3. server.networks.uuid is now required to be a uuid
282create_v237 = copy.deepcopy(create_v233)
283create_v237['properties']['server']['required'].append('networks')
284create_v237['properties']['server']['properties']['networks'] = {
285 'oneOf': [
286 {
287 'type': 'array',
288 'items': {
289 'type': 'object',
290 'properties': {
291 'fixed_ip': parameter_types.ip_address,
292 'port': {
293 'oneOf': [{'type': 'string', 'format': 'uuid'},
294 {'type': 'null'}]
295 },
296 'uuid': {'type': 'string', 'format': 'uuid'},
297 },
298 'additionalProperties': False,
299 },
300 },
301 {'type': 'string', 'enum': ['none', 'auto']},
302 ],
303}
305# 2.42 builds on 2.37 and re-introduces the tag field to the list of network
306# objects.
307create_v242 = copy.deepcopy(create_v237)
308create_v242['properties']['server']['properties']['networks'] = {
309 'oneOf': [
310 {
311 'type': 'array',
312 'items': {
313 'type': 'object',
314 'properties': {
315 'fixed_ip': parameter_types.ip_address,
316 'port': {
317 'oneOf': [{'type': 'string', 'format': 'uuid'},
318 {'type': 'null'}]
319 },
320 'uuid': {'type': 'string', 'format': 'uuid'},
321 'tag': parameter_types.tag,
322 },
323 'additionalProperties': False,
324 },
325 },
326 {'type': 'string', 'enum': ['none', 'auto']},
327 ],
328}
329create_v242['properties']['server'][
330 'properties']['block_device_mapping_v2']['items'][
331 'properties']['tag'] = parameter_types.tag
333# 2.52 builds on 2.42 and makes the following changes:
334# Allowing adding tags to instances when booting
335create_v252 = copy.deepcopy(create_v242)
336create_v252['properties']['server']['properties']['tags'] = {
337 "type": "array",
338 "items": parameter_types.tag,
339 "maxItems": instance.MAX_TAG_COUNT
340}
342# 2.57 builds on 2.52 and removes the personality parameter.
343create_v257 = copy.deepcopy(create_v252)
344create_v257['properties']['server']['properties'].pop('personality')
346# 2.63 builds on 2.57 and makes the following changes:
347# Allowing adding trusted certificates to instances when booting
348create_v263 = copy.deepcopy(create_v257)
349create_v263['properties']['server']['properties'][
350 'trusted_image_certificates'] = parameter_types.trusted_certs
352# Add volume type in block_device_mapping_v2.
353create_v267 = copy.deepcopy(create_v263)
354create_v267['properties']['server']['properties'][
355 'block_device_mapping_v2']['items'][
356 'properties']['volume_type'] = parameter_types.volume_type
358# Add host and hypervisor_hostname in server
359create_v274 = copy.deepcopy(create_v267)
360create_v274['properties']['server'][
361 'properties']['host'] = parameter_types.fqdn
362create_v274['properties']['server'][
363 'properties']['hypervisor_hostname'] = parameter_types.fqdn
365# Add hostname in server
366create_v290 = copy.deepcopy(create_v274)
367create_v290['properties']['server'][
368 'properties']['hostname'] = parameter_types.hostname
370# Support FQDN as hostname
371create_v294 = copy.deepcopy(create_v290)
372create_v294['properties']['server'][
373 'properties']['hostname'] = parameter_types.fqdn
375update = {
376 'type': 'object',
377 'properties': {
378 'server': {
379 'type': 'object',
380 'properties': {
381 'name': parameter_types.name,
382 'OS-DCF:diskConfig': parameter_types.disk_config,
383 'accessIPv4': parameter_types.accessIPv4,
384 'accessIPv6': parameter_types.accessIPv6,
385 },
386 'additionalProperties': False,
387 },
388 },
389 'required': ['server'],
390 'additionalProperties': False,
391}
393update_v20 = copy.deepcopy(update)
394update_v20['properties']['server'][
395 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces
397update_v219 = copy.deepcopy(update)
398update_v219['properties']['server'][
399 'properties']['description'] = parameter_types.description
402update_v290 = copy.deepcopy(update_v219)
403update_v290['properties']['server'][
404 'properties']['hostname'] = parameter_types.hostname
407update_v294 = copy.deepcopy(update_v290)
408update_v294['properties']['server'][
409 'properties']['hostname'] = parameter_types.fqdn
411rebuild = {
412 'type': 'object',
413 'properties': {
414 'rebuild': {
415 'type': 'object',
416 'properties': {
417 'name': parameter_types.name,
418 'imageRef': parameter_types.image_id,
419 'adminPass': parameter_types.admin_password,
420 'metadata': parameter_types.metadata,
421 'preserve_ephemeral': parameter_types.boolean,
422 'OS-DCF:diskConfig': parameter_types.disk_config,
423 'accessIPv4': parameter_types.accessIPv4,
424 'accessIPv6': parameter_types.accessIPv6,
425 'personality': parameter_types.personality,
426 },
427 'required': ['imageRef'],
428 'additionalProperties': False,
429 },
430 },
431 'required': ['rebuild'],
432 'additionalProperties': False,
433}
435rebuild_v20 = copy.deepcopy(rebuild)
436rebuild_v20['properties']['rebuild'][
437 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces
439rebuild_v219 = copy.deepcopy(rebuild)
440rebuild_v219['properties']['rebuild'][
441 'properties']['description'] = parameter_types.description
443rebuild_v254 = copy.deepcopy(rebuild_v219)
444rebuild_v254['properties']['rebuild'][
445 'properties']['key_name'] = parameter_types.name_or_none
447# 2.57 builds on 2.54 and makes the following changes:
448# 1. Remove the personality parameter.
449# 2. Add the user_data parameter which is nullable so user_data can be reset.
450rebuild_v257 = copy.deepcopy(rebuild_v254)
451rebuild_v257['properties']['rebuild']['properties'].pop('personality')
452rebuild_v257['properties']['rebuild']['properties']['user_data'] = ({
453 'oneOf': [
454 {'type': 'string', 'format': 'base64', 'maxLength': 65535},
455 {'type': 'null'}
456 ]
457})
459# 2.63 builds on 2.57 and makes the following changes:
460# Allowing adding trusted certificates to instances when rebuilding
461rebuild_v263 = copy.deepcopy(rebuild_v257)
462rebuild_v263['properties']['rebuild']['properties'][
463 'trusted_image_certificates'] = parameter_types.trusted_certs
465rebuild_v290 = copy.deepcopy(rebuild_v263)
466rebuild_v290['properties']['rebuild']['properties'][
467 'hostname'] = parameter_types.hostname
469rebuild_v294 = copy.deepcopy(rebuild_v290)
470rebuild_v294['properties']['rebuild']['properties'][
471 'hostname'] = parameter_types.fqdn
473resize = {
474 'type': 'object',
475 'properties': {
476 'resize': {
477 'type': 'object',
478 'properties': {
479 'flavorRef': parameter_types.flavor_ref,
480 'OS-DCF:diskConfig': parameter_types.disk_config,
481 },
482 'required': ['flavorRef'],
483 'additionalProperties': False,
484 },
485 },
486 'required': ['resize'],
487 'additionalProperties': False,
488}
490create_image = {
491 'type': 'object',
492 'properties': {
493 'createImage': {
494 'type': 'object',
495 'properties': {
496 'name': parameter_types.name,
497 'metadata': parameter_types.metadata
498 },
499 'required': ['name'],
500 'additionalProperties': False
501 }
502 },
503 'required': ['createImage'],
504 'additionalProperties': False
505}
507create_image_v20 = copy.deepcopy(create_image)
508create_image_v20['properties']['createImage'][
509 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces
511# TODO(stephenfin): Restrict the value to 'null' in a future API version
512confirm_resize = {
513 'type': 'object',
514 'properties': {
515 'confirmResize': {}
516 },
517 'required': ['confirmResize'],
518 'additionalProperties': False
519}
521# TODO(stephenfin): Restrict the value to 'null' in a future API version
522revert_resize = {
523 'type': 'object',
524 'properties': {
525 'revertResize': {},
526 },
527 'required': ['revertResize'],
528 'additionalProperties': False,
529}
531reboot = {
532 'type': 'object',
533 'properties': {
534 'reboot': {
535 'type': 'object',
536 'properties': {
537 'type': {
538 'type': 'string',
539 'enum': ['HARD', 'Hard', 'hard', 'SOFT', 'Soft', 'soft']
540 }
541 },
542 'required': ['type'],
543 'additionalProperties': False
544 }
545 },
546 'required': ['reboot'],
547 'additionalProperties': False
548}
550# TODO(stephenfin): Restrict the value to 'null' in a future API version
551start_server = {
552 'type': 'object',
553 'properties': {
554 'os-start': {},
555 },
556 'required': ['os-start'],
557 'additionalProperties': False,
558}
560# TODO(stephenfin): Restrict the value to 'null' in a future API version
561stop_server = {
562 'type': 'object',
563 'properties': {
564 'os-stop': {},
565 },
566 'required': ['os-stop'],
567 'additionalProperties': False,
568}
570trigger_crash_dump = {
571 'type': 'object',
572 'properties': {
573 'trigger_crash_dump': {
574 'type': 'null'
575 }
576 },
577 'required': ['trigger_crash_dump'],
578 'additionalProperties': False
579}
581JOINED_TABLE_QUERY_PARAMS_SERVERS = {
582 'block_device_mapping': parameter_types.common_query_param,
583 'services': parameter_types.common_query_param,
584 'metadata': parameter_types.common_query_param,
585 'system_metadata': parameter_types.common_query_param,
586 'info_cache': parameter_types.common_query_param,
587 'security_groups': parameter_types.common_query_param,
588 'pci_devices': parameter_types.common_query_param
589}
591# These fields are valid values for sort_keys before we start
592# using schema validation, but are considered to be bad values
593# and disabled to use. In order to avoid backward incompatibility,
594# they are ignored instead of return HTTP 400.
595SERVER_LIST_IGNORE_SORT_KEY = [
596 'architecture', 'cell_name', 'cleaned', 'default_ephemeral_device',
597 'default_swap_device', 'deleted', 'deleted_at', 'disable_terminate',
598 'ephemeral_gb', 'ephemeral_key_uuid', 'id', 'key_data', 'launched_on',
599 'locked', 'memory_mb', 'os_type', 'reservation_id', 'root_gb',
600 'shutdown_terminate', 'user_data', 'vcpus', 'vm_mode'
601]
603# From microversion 2.73 we start offering locked as a valid sort key.
604SERVER_LIST_IGNORE_SORT_KEY_V273 = list(SERVER_LIST_IGNORE_SORT_KEY)
605SERVER_LIST_IGNORE_SORT_KEY_V273.remove('locked')
607VALID_SORT_KEYS = {
608 "type": "string",
609 "enum": ['access_ip_v4', 'access_ip_v6', 'auto_disk_config',
610 'availability_zone', 'config_drive', 'created_at',
611 'display_description', 'display_name', 'host', 'hostname',
612 'image_ref', 'instance_type_id', 'kernel_id', 'key_name',
613 'launch_index', 'launched_at', 'locked_by', 'node', 'power_state',
614 'progress', 'project_id', 'ramdisk_id', 'root_device_name',
615 'task_state', 'terminated_at', 'updated_at', 'user_id', 'uuid',
616 'vm_state'] +
617 SERVER_LIST_IGNORE_SORT_KEY
618}
620# We reuse the existing list and add locked to the list of valid sort keys.
621VALID_SORT_KEYS_V273 = {
622 "type": "string",
623 "enum": ['locked'] + list(
624 set(VALID_SORT_KEYS["enum"]) - set(SERVER_LIST_IGNORE_SORT_KEY)) +
625 SERVER_LIST_IGNORE_SORT_KEY_V273
626}
628query_params_v21 = {
629 'type': 'object',
630 'properties': {
631 'user_id': parameter_types.common_query_param,
632 'project_id': parameter_types.common_query_param,
633 # The alias of project_id. It should be removed in the
634 # future with microversion bump.
635 'tenant_id': parameter_types.common_query_param,
636 'launch_index': parameter_types.common_query_param,
637 # The alias of image. It should be removed in the
638 # future with microversion bump.
639 'image_ref': parameter_types.common_query_param,
640 'image': parameter_types.common_query_param,
641 'kernel_id': parameter_types.common_query_regex_param,
642 'ramdisk_id': parameter_types.common_query_regex_param,
643 'hostname': parameter_types.common_query_regex_param,
644 'key_name': parameter_types.common_query_regex_param,
645 'power_state': parameter_types.common_query_regex_param,
646 'vm_state': parameter_types.common_query_param,
647 'task_state': parameter_types.common_query_param,
648 'host': parameter_types.common_query_param,
649 'node': parameter_types.common_query_regex_param,
650 'flavor': parameter_types.common_query_regex_param,
651 'reservation_id': parameter_types.common_query_regex_param,
652 'launched_at': parameter_types.common_query_regex_param,
653 'terminated_at': parameter_types.common_query_regex_param,
654 'availability_zone': parameter_types.common_query_regex_param,
655 # NOTE(alex_xu): This is pattern matching, it didn't get any benefit
656 # from DB index.
657 'name': parameter_types.common_query_regex_param,
658 # The alias of name. It should be removed in the future
659 # with microversion bump.
660 'display_name': parameter_types.common_query_regex_param,
661 'description': parameter_types.common_query_regex_param,
662 # The alias of description. It should be removed in the
663 # future with microversion bump.
664 'display_description': parameter_types.common_query_regex_param,
665 'locked_by': parameter_types.common_query_regex_param,
666 'uuid': parameter_types.common_query_param,
667 'root_device_name': parameter_types.common_query_regex_param,
668 'config_drive': parameter_types.common_query_regex_param,
669 'access_ip_v4': parameter_types.common_query_regex_param,
670 'access_ip_v6': parameter_types.common_query_regex_param,
671 'auto_disk_config': parameter_types.common_query_regex_param,
672 'progress': parameter_types.common_query_regex_param,
673 'sort_key': multi_params(VALID_SORT_KEYS),
674 'sort_dir': parameter_types.common_query_param,
675 'all_tenants': parameter_types.common_query_param,
676 'soft_deleted': parameter_types.common_query_param,
677 'deleted': parameter_types.common_query_param,
678 'status': parameter_types.common_query_param,
679 'changes-since': multi_params({'type': 'string',
680 'format': 'date-time'}),
681 # NOTE(alex_xu): The ip and ip6 are implemented in the python.
682 'ip': parameter_types.common_query_regex_param,
683 'ip6': parameter_types.common_query_regex_param,
684 'created_at': parameter_types.common_query_regex_param,
685 },
686 # For backward-compatible additionalProperties is set to be True here.
687 # And we will either strip the extra params out or raise HTTP 400
688 # according to the params' value in the later process.
689 # This has been changed to False in microversion 2.75. From
690 # microversion 2.75, no additional unknown parameter will be allowed.
691 'additionalProperties': True,
692 # Prevent internal-attributes that are started with underscore from
693 # being striped out in schema validation, and raise HTTP 400 in API.
694 'patternProperties': {"^_": parameter_types.common_query_param}
695}
697# Update the joined-table fields to the list so it will not be
698# stripped in later process, thus can be handled later in api
699# to raise HTTP 400.
700query_params_v21['properties'].update(
701 JOINED_TABLE_QUERY_PARAMS_SERVERS)
703query_params_v21['properties'].update(
704 parameter_types.pagination_parameters)
706query_params_v226 = copy.deepcopy(query_params_v21)
707query_params_v226['properties'].update({
708 'tags': parameter_types.common_query_regex_param,
709 'tags-any': parameter_types.common_query_regex_param,
710 'not-tags': parameter_types.common_query_regex_param,
711 'not-tags-any': parameter_types.common_query_regex_param,
712})
714query_params_v266 = copy.deepcopy(query_params_v226)
715query_params_v266['properties'].update({
716 'changes-before': multi_params({'type': 'string',
717 'format': 'date-time'}),
718})
720query_params_v273 = copy.deepcopy(query_params_v266)
721query_params_v273['properties'].update({
722 'sort_key': multi_params(VALID_SORT_KEYS_V273),
723 'locked': parameter_types.common_query_param,
724})
726# Microversion 2.75 makes query schema to disallow any invalid or unknown
727# query parameters (filter or sort keys).
728# *****Schema updates for microversion 2.75 start here*******
729query_params_v275 = copy.deepcopy(query_params_v273)
730# 1. Update sort_keys to allow only valid sort keys:
731# NOTE(gmann): Remove the ignored sort keys now because 'additionalProperties'
732# is False for query schema. Starting from miceoversion 2.75, API will
733# raise 400 for any not-allowed sort keys instead of ignoring them.
734VALID_SORT_KEYS_V275 = copy.deepcopy(VALID_SORT_KEYS_V273)
735VALID_SORT_KEYS_V275['enum'] = list(
736 set(VALID_SORT_KEYS_V273["enum"]) - set(
737 SERVER_LIST_IGNORE_SORT_KEY_V273))
738query_params_v275['properties'].update({
739 'sort_key': multi_params(VALID_SORT_KEYS_V275),
740})
741# 2. Make 'additionalProperties' False.
742query_params_v275['additionalProperties'] = False
743# *****Schema updates for microversion 2.75 end here*******
745show_query = {
746 'type': 'object',
747 'properties': {},
748 'additionalProperties': True,
749}
751resize_response = {
752 'type': 'null',
753}
755confirm_resize_response = {
756 'type': 'null',
757}
759revert_resize_response = {
760 'type': 'null',
761}
763reboot_response = {
764 'type': 'null',
765}
767start_server_response = {
768 'type': 'null',
769}
771stop_server_response = {
772 'type': 'null',
773}
775trigger_crash_dump_response = {
776 'type': 'null',
777}
779create_image_response = {
780 'type': 'null',
781}
783create_image_response_v245 = {
784 'type': 'object',
785 'properties': {
786 'image_id': {'type': 'string', 'format': 'uuid'},
787 },
788 'required': ['image_id'],
789 'additionalProperties': False,
790}
792rebuild_response = {
793 'type': 'object',
794 'properties': {
795 'server': {
796 'type': 'object',
797 'properties': {
798 'accessIPv4': {
799 'type': 'string',
800 'oneOf': [
801 {'format': 'ipv4'},
802 {'const': ''},
803 ],
804 },
805 'accessIPv6': {
806 'type': 'string',
807 'oneOf': [
808 {'format': 'ipv6'},
809 {'const': ''},
810 ],
811 },
812 'addresses': {
813 'type': 'object',
814 'patternProperties': {
815 '^.+$': {
816 'type': 'array',
817 'items': {
818 'type': 'object',
819 'properties': {
820 'addr': {
821 'type': 'string',
822 'oneOf': [
823 {'format': 'ipv4'},
824 {'format': 'ipv6'},
825 ],
826 },
827 'version': {
828 'type': 'number',
829 'enum': [4, 6],
830 },
831 },
832 'required': [
833 'addr',
834 'version'
835 ],
836 'additionalProperties': False,
837 },
838 },
839 },
840 'additionalProperties': False,
841 },
842 'adminPass': {'type': ['null', 'string']},
843 'created': {'type': 'string', 'format': 'date-time'},
844 'fault': {
845 'type': 'object',
846 'properties': {
847 'code': {'type': 'integer'},
848 'created': {'type': 'string', 'format': 'date-time'},
849 'details': {'type': 'string'},
850 'message': {'type': 'string'},
851 },
852 'required': ['code', 'created', 'message'],
853 'additionalProperties': False,
854 },
855 'flavor': {
856 'type': 'object',
857 'properties': {
858 'id': {
859 'type': 'string',
860 },
861 'links': {
862 'type': 'array',
863 'items': {
864 'type': 'object',
865 'properties': {
866 'href': {
867 'type': 'string',
868 'format': 'uri',
869 },
870 'rel': {
871 'type': 'string',
872 },
873 },
874 'required': [
875 'href',
876 'rel'
877 ],
878 "additionalProperties": False,
879 },
880 },
881 },
882 'additionalProperties': False,
883 },
884 'hostId': {'type': 'string'},
885 'id': {'type': 'string'},
886 'image': {
887 'oneOf': [
888 {
889 'type': 'string',
890 'const': '',
891 },
892 {
893 'type': 'object',
894 'properties': {
895 'id': {
896 'type': 'string'
897 },
898 'links': {
899 'type': 'array',
900 'items': {
901 'type': 'object',
902 'properties': {
903 'href': {
904 'type': 'string',
905 'format': 'uri',
906 },
907 'rel': {
908 'type': 'string',
909 },
910 },
911 'required': [
912 'href',
913 'rel'
914 ],
915 "additionalProperties": False,
916 },
917 },
918 },
919 'additionalProperties': False,
920 },
921 ],
922 },
923 'links': {
924 'type': 'array',
925 'items': {
926 'type': 'object',
927 'properties': {
928 'href': {
929 'type': 'string',
930 'format': 'uri',
931 },
932 'rel': {
933 'type': 'string',
934 },
935 },
936 'required': [
937 'href',
938 'rel'
939 ],
940 'additionalProperties': False,
941 },
942 },
943 'metadata': {
944 'type': 'object',
945 'patternProperties': {
946 '^.+$': {
947 'type': 'string'
948 },
949 },
950 'additionalProperties': False,
951 },
952 'name': {'type': ['string', 'null']},
953 'progress': {'type': ['null', 'number']},
954 'status': {'type': 'string'},
955 'tenant_id': parameter_types.project_id,
956 'updated': {'type': 'string', 'format': 'date-time'},
957 'user_id': parameter_types.user_id,
958 'OS-DCF:diskConfig': {'type': 'string'},
959 },
960 'required': [
961 'accessIPv4',
962 'accessIPv6',
963 'addresses',
964 'created',
965 'flavor',
966 'hostId',
967 'id',
968 'image',
969 'links',
970 'metadata',
971 'name',
972 'progress',
973 'status',
974 'tenant_id',
975 'updated',
976 'user_id',
977 'OS-DCF:diskConfig',
978 ],
979 'additionalProperties': False,
980 },
981 },
982 'required': [
983 'server'
984 ],
985 'additionalProperties': False,
986}
988rebuild_response_v29 = copy.deepcopy(rebuild_response)
989rebuild_response_v29['properties']['server']['properties']['locked'] = {
990 'type': 'boolean',
991}
992rebuild_response_v29['properties']['server']['required'].append('locked')
994rebuild_response_v219 = copy.deepcopy(rebuild_response_v29)
995rebuild_response_v219['properties']['server']['properties']['description'] = {
996 'type': ['null', 'string'],
997}
998rebuild_response_v219['properties']['server']['required'].append('description')
1000rebuild_response_v226 = copy.deepcopy(rebuild_response_v219)
1001rebuild_response_v226['properties']['server']['properties']['tags'] = {
1002 'type': 'array',
1003 'items': {
1004 'type': 'string',
1005 },
1006 'maxItems': 50,
1007}
1008rebuild_response_v226['properties']['server']['required'].append('tags')
1010# NOTE(stephenfin): We overwrite rather than extend 'flavor', since we now
1011# embed the flavor in this version
1012rebuild_response_v246 = copy.deepcopy(rebuild_response_v226)
1013rebuild_response_v246['properties']['server']['properties']['flavor'] = {
1014 'type': 'object',
1015 'properties': {
1016 'vcpus': {
1017 'type': 'integer',
1018 },
1019 'ram': {
1020 'type': 'integer',
1021 },
1022 'disk': {
1023 'type': 'integer',
1024 },
1025 'ephemeral': {
1026 'type': 'integer',
1027 },
1028 'swap': {
1029 'type': 'integer',
1030 },
1031 'original_name': {
1032 'type': 'string',
1033 },
1034 'extra_specs': {
1035 'type': 'object',
1036 'patternProperties': {
1037 '^.+$': {
1038 'type': 'string'
1039 },
1040 },
1041 'additionalProperties': False,
1042 },
1043 },
1044 'required': ['vcpus', 'ram', 'disk', 'ephemeral', 'swap', 'original_name'],
1045 'additionalProperties': False,
1046}
1048rebuild_response_v254 = copy.deepcopy(rebuild_response_v246)
1049rebuild_response_v254['properties']['server']['properties']['key_name'] = {
1050 'type': ['null', 'string'],
1051}
1052rebuild_response_v254['properties']['server']['required'].append('key_name')
1054rebuild_response_v257 = copy.deepcopy(rebuild_response_v254)
1055rebuild_response_v257['properties']['server']['properties']['user_data'] = {
1056 'oneOf': [
1057 {'type': 'string', 'format': 'base64', 'maxLength': 65535},
1058 {'type': 'null'},
1059 ],
1060}
1061rebuild_response_v257['properties']['server']['required'].append('user_data')
1063rebuild_response_v263 = copy.deepcopy(rebuild_response_v257)
1064rebuild_response_v263['properties']['server']['properties'].update(
1065 {
1066 'trusted_image_certificates': {
1067 'type': ['array', 'null'],
1068 'items': {
1069 'type': 'string',
1070 },
1071 },
1072 },
1073)
1074rebuild_response_v263['properties']['server']['required'].append(
1075 'trusted_image_certificates'
1076)
1078rebuild_response_v271 = copy.deepcopy(rebuild_response_v263)
1079rebuild_response_v271['properties']['server']['properties'].update(
1080 {
1081 'server_groups': {
1082 'type': 'array',
1083 'items': {
1084 'type': 'string',
1085 'format': 'uuid',
1086 },
1087 'maxLength': 1,
1088 },
1089 },
1090)
1091rebuild_response_v271['properties']['server']['required'].append(
1092 'server_groups'
1093)
1095rebuild_response_v273 = copy.deepcopy(rebuild_response_v271)
1096rebuild_response_v273['properties']['server']['properties'].update(
1097 {
1098 'locked_reason': {
1099 'type': ['null', 'string'],
1100 },
1101 },
1102)
1103rebuild_response_v273['properties']['server']['required'].append(
1104 'locked_reason'
1105)
1107rebuild_response_v275 = copy.deepcopy(rebuild_response_v273)
1108rebuild_response_v275['properties']['server']['properties'].update(
1109 {
1110 'config_drive': {
1111 # TODO(stephenfin): Our tests return null but this shouldn't happen
1112 # in practice, apparently?
1113 'type': ['string', 'boolean', 'null'],
1114 },
1115 'OS-EXT-AZ:availability_zone': {
1116 'type': 'string',
1117 },
1118 'OS-EXT-SRV-ATTR:host': {
1119 'type': ['string', 'null'],
1120 },
1121 'OS-EXT-SRV-ATTR:hypervisor_hostname': {
1122 'type': ['string', 'null'],
1123 },
1124 'OS-EXT-SRV-ATTR:instance_name': {
1125 'type': 'string',
1126 },
1127 'OS-EXT-STS:power_state': {
1128 'type': 'integer',
1129 'enum': [0, 1, 3, 4, 6, 7],
1130 },
1131 'OS-EXT-STS:task_state': {
1132 'type': ['null', 'string'],
1133 },
1134 'OS-EXT-STS:vm_state': {
1135 'type': 'string',
1136 },
1137 'OS-EXT-SRV-ATTR:hostname': {
1138 'type': 'string',
1139 },
1140 'OS-EXT-SRV-ATTR:reservation_id': {
1141 'type': ['string', 'null'],
1142 },
1143 'OS-EXT-SRV-ATTR:launch_index': {
1144 'type': 'integer',
1145 },
1146 'OS-EXT-SRV-ATTR:kernel_id': {
1147 'type': ['string', 'null'],
1148 },
1149 'OS-EXT-SRV-ATTR:ramdisk_id': {
1150 'type': ['string', 'null'],
1151 },
1152 'OS-EXT-SRV-ATTR:root_device_name': {
1153 'type': ['string', 'null'],
1154 },
1155 'os-extended-volumes:volumes_attached': {
1156 'type': 'array',
1157 'items': {
1158 'type': 'object',
1159 'properties': {
1160 'id': {
1161 'type': 'string',
1162 },
1163 'delete_on_termination': {
1164 'type': 'boolean',
1165 'default': False,
1166 },
1167 },
1168 'required': ['id', 'delete_on_termination'],
1169 'additionalProperties': False,
1170 },
1171 },
1172 'OS-SRV-USG:launched_at': {
1173 'oneOf': [
1174 {'type': 'null'},
1175 {'type': 'string', 'format': 'date-time'},
1176 ],
1177 },
1178 'OS-SRV-USG:terminated_at': {
1179 'oneOf': [
1180 {'type': 'null'},
1181 {'type': 'string', 'format': 'date-time'},
1182 ],
1183 },
1184 'security_groups': {
1185 'type': 'array',
1186 'items': {
1187 'type': 'object',
1188 'properties': {
1189 'name': {
1190 'type': 'string',
1191 },
1192 },
1193 'required': ['name'],
1194 'additionalProperties': False,
1195 },
1196 },
1197 'host_status': {
1198 'type': 'string',
1199 },
1200 },
1201)
1202rebuild_response_v275['properties']['server']['required'].extend([
1203 'config_drive',
1204 'OS-EXT-AZ:availability_zone',
1205 'OS-EXT-STS:power_state',
1206 'OS-EXT-STS:task_state',
1207 'OS-EXT-STS:vm_state',
1208 'os-extended-volumes:volumes_attached',
1209 'OS-SRV-USG:launched_at',
1210 'OS-SRV-USG:terminated_at',
1211])
1212rebuild_response_v275['properties']['server']['properties']['addresses'][
1213 'patternProperties'
1214]['^.+$']['items']['properties'].update({
1215 'OS-EXT-IPS-MAC:mac_addr': {'type': 'string', 'format': 'mac-address'},
1216 'OS-EXT-IPS:type': {'type': 'string', 'enum': ['fixed', 'floating']},
1217})
1218rebuild_response_v275['properties']['server']['properties']['addresses'][
1219 'patternProperties'
1220]['^.+$']['items']['required'].extend([
1221 'OS-EXT-IPS-MAC:mac_addr', 'OS-EXT-IPS:type'
1222])
1224rebuild_response_v296 = copy.deepcopy(rebuild_response_v275)
1225rebuild_response_v296['properties']['server']['properties'].update({
1226 'pinned_availability_zone': {
1227 'type': ['null', 'string'],
1228 },
1229})
1230rebuild_response_v296['properties']['server']['required'].append(
1231 'pinned_availability_zone'
1232)
1233rebuild_response_v298 = copy.deepcopy(rebuild_response_v296)
1234rebuild_response_v298['properties']['server']['properties']['image'][
1235 'oneOf'][1]['properties'].update({
1236 'properties': {
1237 'type': 'object',
1238 'patternProperties': {
1239 '^[a-zA-Z0-9_:. ]{1,255}$': {
1240 'type': 'string',
1241 'max_Length': 255,
1242 },
1243 },
1244 'additionalProperties': False,
1245 },
1246})
1248rebuild_response_v2100 = copy.deepcopy(rebuild_response_v298)
1249rebuild_response_v2100['properties']['server']['properties'].update({
1250 'scheduler_hints': _hints,
1251})
1252rebuild_response_v2100['properties']['server']['required'].append(
1253 'scheduler_hints'
1254)