|
15 | 15 | VirtInstanceBootableDiskArgs, VirtInstanceBootableDiskResult,
|
16 | 16 | )
|
17 | 17 | from middlewared.async_validators import check_path_resides_within_volume
|
18 |
| -from .utils import get_max_boot_priority_device, incus_call_and_wait, incus_pool_to_storage_pool, storage_pool_to_incus_pool |
| 18 | +from .utils import ( |
| 19 | + get_max_boot_priority_device, incus_call_and_wait, incus_pool_to_storage_pool, storage_pool_to_incus_pool, |
| 20 | + validate_device_name, CDROM_PREFIX, update_instance_metadata_and_qemu_cmd_on_device_change, |
| 21 | +) |
19 | 22 |
|
20 | 23 |
|
21 | 24 | class VirtInstanceDeviceService(Service):
|
@@ -62,15 +65,23 @@ async def incus_to_device(self, name: str, incus: dict[str, Any], context: dict)
|
62 | 65 |
|
63 | 66 | match incus['type']:
|
64 | 67 | case 'disk':
|
65 |
| - device.update({ |
66 |
| - 'dev_type': 'DISK', |
67 |
| - 'source': incus.get('source'), |
68 |
| - 'storage_pool': incus_pool_to_storage_pool(incus.get('pool')), |
69 |
| - 'destination': incus.get('path'), |
70 |
| - 'description': f'{incus.get("source")} -> {incus.get("path")}', |
71 |
| - 'boot_priority': int(incus['boot.priority']) if incus.get('boot.priority') else None, |
72 |
| - 'io_bus': incus['io.bus'].upper() if incus.get('io.bus') else None, |
73 |
| - }) |
| 68 | + if name.startswith(CDROM_PREFIX): |
| 69 | + device.update({ |
| 70 | + 'dev_type': 'CDROM', |
| 71 | + 'source': incus.get('source'), |
| 72 | + 'description': f'{incus.get("source")!r} CDROM device source', |
| 73 | + 'boot_priority': int(incus['boot.priority']) if incus.get('boot.priority') else None, |
| 74 | + }) |
| 75 | + else: |
| 76 | + device.update({ |
| 77 | + 'dev_type': 'DISK', |
| 78 | + 'source': incus.get('source'), |
| 79 | + 'storage_pool': incus_pool_to_storage_pool(incus.get('pool')), |
| 80 | + 'destination': incus.get('path'), |
| 81 | + 'description': f'{incus.get("source")} -> {incus.get("path")}', |
| 82 | + 'boot_priority': int(incus['boot.priority']) if incus.get('boot.priority') else None, |
| 83 | + 'io_bus': incus['io.bus'].upper() if incus.get('io.bus') else None, |
| 84 | + }) |
74 | 85 | case 'nic':
|
75 | 86 | device.update({
|
76 | 87 | 'dev_type': 'NIC',
|
@@ -195,7 +206,14 @@ async def device_to_incus(self, instance_type: str, device: dict[str, Any]) -> d
|
195 | 206 | new['pool'] = None
|
196 | 207 | if device.get('io_bus'):
|
197 | 208 | new['io.bus'] = device['io_bus'].lower()
|
198 |
| - |
| 209 | + case 'CDROM': |
| 210 | + new |= { |
| 211 | + 'type': 'disk', |
| 212 | + 'source': device['source'], |
| 213 | + 'path': None, |
| 214 | + } |
| 215 | + if device['boot_priority'] is not None: |
| 216 | + new['boot.priority'] = str(device['boot_priority']) |
199 | 217 | case 'NIC':
|
200 | 218 | new.update({
|
201 | 219 | 'type': 'nic',
|
@@ -266,6 +284,9 @@ async def generate_device_name(self, device_names: list[str], device_type: str)
|
266 | 284 | name = device_type.lower()
|
267 | 285 | if name == 'nic':
|
268 | 286 | name = 'eth'
|
| 287 | + elif name == 'cdrom': |
| 288 | + name = CDROM_PREFIX |
| 289 | + |
269 | 290 | i = 0
|
270 | 291 | while True:
|
271 | 292 | new_name = f'{name}{i}'
|
@@ -323,6 +344,16 @@ async def validate_device(
|
323 | 344 | )
|
324 | 345 | verrors.extend(verror)
|
325 | 346 | break
|
| 347 | + case 'CDROM': |
| 348 | + source = device['source'] |
| 349 | + if os.path.isabs(source) is False: |
| 350 | + verrors.add(schema, 'Source must be an absolute path') |
| 351 | + if await self.middleware.run_in_thread(os.path.exists, source) is False: |
| 352 | + verrors.add(schema, 'Specified source path does not exist') |
| 353 | + elif await self.middleware.run_in_thread(os.path.isfile, source) is False: |
| 354 | + verrors.add(schema, 'Specified source path is not a file') |
| 355 | + if instance_type == 'CONTAINER': |
| 356 | + verrors.add(schema, 'Container instance type is not supported') |
326 | 357 | case 'DISK':
|
327 | 358 | source = device['source'] or ''
|
328 | 359 | if source == '' and device['name'] != 'root':
|
@@ -464,71 +495,93 @@ async def get_all_disk_sources_of_instance(self, instance_name):
|
464 | 495 | VirtInstanceDeviceAddArgs,
|
465 | 496 | VirtInstanceDeviceAddResult,
|
466 | 497 | audit='Virt: Adding device',
|
467 |
| - audit_extended=lambda id, device: f'{device["dev_type"]!r} to {id!r} instance', |
| 498 | + audit_extended=lambda i, device: f'{device["dev_type"]!r} to {i!r} instance', |
468 | 499 | roles=['VIRT_INSTANCE_WRITE']
|
469 | 500 | )
|
470 |
| - async def device_add(self, id, device): |
| 501 | + async def device_add(self, oid, device): |
471 | 502 | """
|
472 | 503 | Add a device to an instance.
|
473 | 504 | """
|
474 |
| - instance = await self.middleware.call('virt.instance.get_instance', id, {'extra': {'raw': True}}) |
| 505 | + instance = await self.middleware.call('virt.instance.get_instance', oid, {'extra': {'raw': True}}) |
475 | 506 | data = instance['raw']
|
| 507 | + verrors = ValidationErrors() |
| 508 | + validate_device_name(device, verrors) |
476 | 509 | if device['name'] is None:
|
477 | 510 | device['name'] = await self.generate_device_name(data['devices'].keys(), device['dev_type'])
|
478 | 511 |
|
479 |
| - verrors = ValidationErrors() |
480 |
| - await self.validate_device(device, 'virt_device_add', verrors, id, instance['type'], instance_config=instance) |
| 512 | + await self.validate_device(device, 'virt_device_add', verrors, oid, instance['type'], instance_config=instance) |
481 | 513 | verrors.check()
|
482 | 514 |
|
483 | 515 | data['devices'][device['name']] = await self.device_to_incus(instance['type'], device)
|
484 |
| - await incus_call_and_wait(f'1.0/instances/{id}', 'put', {'json': data}) |
| 516 | + if device['dev_type'] == 'CDROM': |
| 517 | + # We want to update qemu config here and make sure we keep track of which |
| 518 | + # devices we have added as cdroms here |
| 519 | + data['config'].update(update_instance_metadata_and_qemu_cmd_on_device_change( |
| 520 | + oid, data['config'], data['devices'] |
| 521 | + )) |
| 522 | + |
| 523 | + await incus_call_and_wait(f'1.0/instances/{oid}', 'put', {'json': data}) |
485 | 524 | return True
|
486 | 525 |
|
487 | 526 | @api_method(
|
488 | 527 | VirtInstanceDeviceUpdateArgs,
|
489 | 528 | VirtInstanceDeviceUpdateResult,
|
490 | 529 | audit='Virt: Updating device',
|
491 |
| - audit_extended=lambda id, device: f'{device["name"]!r} of {id!r} instance', |
| 530 | + audit_extended=lambda i, device: f'{device["name"]!r} of {i!r} instance', |
492 | 531 | roles=['VIRT_INSTANCE_WRITE']
|
493 | 532 | )
|
494 |
| - async def device_update(self, id, device): |
| 533 | + async def device_update(self, oid, device): |
495 | 534 | """
|
496 | 535 | Update a device in an instance.
|
497 | 536 | """
|
498 |
| - instance = await self.middleware.call('virt.instance.get_instance', id, {'extra': {'raw': True}}) |
| 537 | + instance = await self.middleware.call('virt.instance.get_instance', oid, {'extra': {'raw': True}}) |
499 | 538 | data = instance['raw']
|
500 | 539 |
|
501 |
| - for old in await self.device_list(id): |
| 540 | + for old in await self.device_list(oid): |
502 | 541 | if old['name'] == device['name']:
|
503 | 542 | break
|
504 | 543 | else:
|
505 | 544 | raise CallError('Device does not exist.', errno.ENOENT)
|
506 | 545 |
|
507 | 546 | verrors = ValidationErrors()
|
508 |
| - await self.validate_device(device, 'virt_device_update', verrors, id, instance['type'], old, instance) |
| 547 | + await self.validate_device(device, 'virt_device_update', verrors, oid, instance['type'], old, instance) |
509 | 548 | verrors.check()
|
510 | 549 |
|
511 | 550 | data['devices'][device['name']] = await self.device_to_incus(instance['type'], device)
|
512 |
| - await incus_call_and_wait(f'1.0/instances/{id}', 'put', {'json': data}) |
| 551 | + if device['dev_type'] == 'CDROM': |
| 552 | + # We want to update qemu config here and make sure we keep track of which |
| 553 | + # devices we have added as cdroms here |
| 554 | + data['config'].update(update_instance_metadata_and_qemu_cmd_on_device_change( |
| 555 | + oid, data['config'], data['devices'] |
| 556 | + )) |
| 557 | + |
| 558 | + await incus_call_and_wait(f'1.0/instances/{oid}', 'put', {'json': data}) |
513 | 559 | return True
|
514 | 560 |
|
515 | 561 | @api_method(
|
516 | 562 | VirtInstanceDeviceDeleteArgs,
|
517 | 563 | VirtInstanceDeviceDeleteResult,
|
518 | 564 | audit='Virt: Deleting device',
|
519 |
| - audit_extended=lambda id, device: f'{device!r} from {id!r} instance', |
| 565 | + audit_extended=lambda i, device: f'{device!r} from {i!r} instance', |
520 | 566 | roles=['VIRT_INSTANCE_DELETE']
|
521 | 567 | )
|
522 |
| - async def device_delete(self, id, device): |
| 568 | + async def device_delete(self, oid, device): |
523 | 569 | """
|
524 | 570 | Delete a device from an instance.
|
525 | 571 | """
|
526 |
| - instance = await self.middleware.call('virt.instance.get_instance', id, {'extra': {'raw': True}}) |
| 572 | + instance = await self.middleware.call('virt.instance.get_instance', oid, {'extra': {'raw': True}}) |
527 | 573 | data = instance['raw']
|
528 | 574 | if device not in data['devices']:
|
529 | 575 | raise CallError('Device not found.', errno.ENOENT)
|
530 | 576 | data['devices'].pop(device)
|
531 |
| - await incus_call_and_wait(f'1.0/instances/{id}', 'put', {'json': data}) |
| 577 | + if device.startswith(CDROM_PREFIX): |
| 578 | + # We want to update qemu config here and make sure we keep track of which |
| 579 | + # devices we have added as cdroms here |
| 580 | + data['config'].update(update_instance_metadata_and_qemu_cmd_on_device_change( |
| 581 | + oid, data['config'], data['devices'] |
| 582 | + )) |
| 583 | + |
| 584 | + await incus_call_and_wait(f'1.0/instances/{oid}', 'put', {'json': data}) |
532 | 585 | return True
|
533 | 586 |
|
534 | 587 | @api_method(
|
@@ -559,16 +612,25 @@ async def set_bootable_disk(self, id, disk):
|
559 | 612 | if desired_disk is None:
|
560 | 613 | raise CallError(f'{disk!r} device does not exist.', errno.ENOENT)
|
561 | 614 |
|
562 |
| - if desired_disk['dev_type'] != 'DISK': |
| 615 | + if desired_disk['dev_type'] not in ('CDROM', 'DISK'): |
563 | 616 | raise CallError(f'{disk!r} device type is not DISK.')
|
564 | 617 |
|
565 | 618 | if max_boot_priority_device and max_boot_priority_device['name'] == disk:
|
566 | 619 | return True
|
567 | 620 |
|
568 |
| - return await self.device_update(id, { |
569 |
| - 'dev_type': 'DISK', |
| 621 | + data = { |
570 | 622 | 'name': disk,
|
571 | 623 | 'source': desired_disk.get('source'),
|
572 |
| - 'io_bus': desired_disk.get('io_bus'), |
573 | 624 | 'boot_priority': max_boot_priority_device['boot_priority'] + 1 if max_boot_priority_device else 1,
|
574 |
| - } | ({'destination': desired_disk['destination']} if disk != 'root' else {})) |
| 625 | + } |
| 626 | + if desired_disk['dev_type'] == 'CDROM': |
| 627 | + data |= { |
| 628 | + 'dev_type': 'CDROM', |
| 629 | + } |
| 630 | + else: |
| 631 | + data |= { |
| 632 | + 'dev_type': 'DISK', |
| 633 | + 'io_bus': desired_disk.get('io_bus'), |
| 634 | + } | ({'destination': desired_disk['destination']} if disk != 'root' else {}) |
| 635 | + |
| 636 | + return await self.device_update(id, data) |
0 commit comments