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

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. 

14 

15import copy 

16 

17from nova.api.validation import parameter_types 

18from nova.api.validation.parameter_types import multi_params 

19from nova.objects import instance 

20 

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} 

47 

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} 

89 

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) 

94 

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} 

150 

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} 

229 

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} 

246 

247create_v219 = copy.deepcopy(create) 

248create_v219['properties']['server'][ 

249 'properties']['description'] = parameter_types.description 

250 

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 

258 

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. 

265 

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 

277 

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} 

304 

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 

332 

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} 

341 

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

345 

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 

351 

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 

357 

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 

364 

365# Add hostname in server 

366create_v290 = copy.deepcopy(create_v274) 

367create_v290['properties']['server'][ 

368 'properties']['hostname'] = parameter_types.hostname 

369 

370# Support FQDN as hostname 

371create_v294 = copy.deepcopy(create_v290) 

372create_v294['properties']['server'][ 

373 'properties']['hostname'] = parameter_types.fqdn 

374 

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} 

392 

393update_v20 = copy.deepcopy(update) 

394update_v20['properties']['server'][ 

395 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces 

396 

397update_v219 = copy.deepcopy(update) 

398update_v219['properties']['server'][ 

399 'properties']['description'] = parameter_types.description 

400 

401 

402update_v290 = copy.deepcopy(update_v219) 

403update_v290['properties']['server'][ 

404 'properties']['hostname'] = parameter_types.hostname 

405 

406 

407update_v294 = copy.deepcopy(update_v290) 

408update_v294['properties']['server'][ 

409 'properties']['hostname'] = parameter_types.fqdn 

410 

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} 

434 

435rebuild_v20 = copy.deepcopy(rebuild) 

436rebuild_v20['properties']['rebuild'][ 

437 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces 

438 

439rebuild_v219 = copy.deepcopy(rebuild) 

440rebuild_v219['properties']['rebuild'][ 

441 'properties']['description'] = parameter_types.description 

442 

443rebuild_v254 = copy.deepcopy(rebuild_v219) 

444rebuild_v254['properties']['rebuild'][ 

445 'properties']['key_name'] = parameter_types.name_or_none 

446 

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

458 

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 

464 

465rebuild_v290 = copy.deepcopy(rebuild_v263) 

466rebuild_v290['properties']['rebuild']['properties'][ 

467 'hostname'] = parameter_types.hostname 

468 

469rebuild_v294 = copy.deepcopy(rebuild_v290) 

470rebuild_v294['properties']['rebuild']['properties'][ 

471 'hostname'] = parameter_types.fqdn 

472 

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} 

489 

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} 

506 

507create_image_v20 = copy.deepcopy(create_image) 

508create_image_v20['properties']['createImage'][ 

509 'properties']['name'] = parameter_types.name_with_leading_trailing_spaces 

510 

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} 

520 

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} 

530 

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} 

549 

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} 

559 

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} 

569 

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} 

580 

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} 

590 

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] 

602 

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

606 

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} 

619 

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} 

627 

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} 

696 

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) 

702 

703query_params_v21['properties'].update( 

704 parameter_types.pagination_parameters) 

705 

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

713 

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

719 

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

725 

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

744 

745show_query = { 

746 'type': 'object', 

747 'properties': {}, 

748 'additionalProperties': True, 

749} 

750 

751resize_response = { 

752 'type': 'null', 

753} 

754 

755confirm_resize_response = { 

756 'type': 'null', 

757} 

758 

759revert_resize_response = { 

760 'type': 'null', 

761} 

762 

763reboot_response = { 

764 'type': 'null', 

765} 

766 

767start_server_response = { 

768 'type': 'null', 

769} 

770 

771stop_server_response = { 

772 'type': 'null', 

773} 

774 

775trigger_crash_dump_response = { 

776 'type': 'null', 

777} 

778 

779create_image_response = { 

780 'type': 'null', 

781} 

782 

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} 

791 

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} 

987 

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

993 

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

999 

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

1009 

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} 

1047 

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

1053 

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

1062 

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) 

1077 

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) 

1094 

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) 

1106 

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

1223 

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

1247 

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)