From c7f296702625adbe3704b3f3b6eeebb1c9c6a7d7 Mon Sep 17 00:00:00 2001 From: James Adams Date: Tue, 22 Jul 2025 18:25:08 +0100 Subject: [PATCH] netbox2aquilon: Add support for multiple VM disks As added in NetBox 3.7. --- aquilon/netbox2aquilon.py | 46 +++++++++++++++++++----- aquilon/scd_netbox.py | 12 +++++++ aquilon/test_netbox2aquilon.py | 37 +++++++++++++++++++ aquilon/testdata/__init__.py | 1 + aquilon/testdata/disks_virtual.json | 56 +++++++++++++++++++++++++++++ requirements.txt | 3 +- 6 files changed, 146 insertions(+), 9 deletions(-) create mode 100644 aquilon/testdata/disks_virtual.json diff --git a/aquilon/netbox2aquilon.py b/aquilon/netbox2aquilon.py index 680b8cd..a2d30e0 100755 --- a/aquilon/netbox2aquilon.py +++ b/aquilon/netbox2aquilon.py @@ -128,6 +128,43 @@ def _netbox_copy_device(self, device): return cmds + def _netbox_copy_vm_disks(self, virtual_machine): + """ + Check if VM has any new-style virtual disks defined, + If so, use them and set the first as bootable, + If not, fall back to the classic single bootable disk method. + """ + cmds = [] + + virtual_disks = self.get_disks_from_device(virtual_machine) + if virtual_disks: + boot = True + for disk in virtual_disks: + cmd = [ + 'add_disk', + '--machine', f'{virtual_machine.aq_machine_name}', + '--disk', f'{disk.name}', + '--controller', 'sata', + '--size', f'{disk.size}', + ] + if boot: + cmd.append('--boot') + boot = False + if disk.description: + cmd += ['--comments', f'"{disk.description}"'] + cmds.append(cmd) + else: + cmds.append([ + 'add_disk', + '--machine', f'{virtual_machine.aq_machine_name}', + '--disk', 'sda', + '--controller', 'sata', + '--size', f'{virtual_machine.disk}', + '--boot', + ]) + + return cmds + def _netbox_copy_vm(self, virtual_machine): cmds = [] @@ -153,14 +190,7 @@ def _netbox_copy_vm(self, virtual_machine): '--memory', f'{virtual_machine.memory}', ]) - cmds.append([ - 'add_disk', - '--machine', f'{virtual_machine.aq_machine_name}', - '--disk', 'sda', - '--controller', 'sata', - '--size', f'{virtual_machine.disk}', - '--boot', - ]) + cmds += self._netbox_copy_vm_disks(virtual_machine) return cmds diff --git a/aquilon/scd_netbox.py b/aquilon/scd_netbox.py index e3c71b3..5c52fee 100644 --- a/aquilon/scd_netbox.py +++ b/aquilon/scd_netbox.py @@ -185,3 +185,15 @@ def get_addresses_from_interface(self, interface): ) return ipv4_addresses + + def get_disks_from_device(self, device): + """ + Get all virtual disks associated with a virtual machine + """ + if isinstance(device, pynetbox.models.virtualization.VirtualMachines): + filtered_disks = self.netbox.virtualization.virtual_disks.filter(virtual_machine_id=device.id) + else: + logging.error('Unsupported device type for disks "%s"', type(device)) + sys.exit(1) + + return filtered_disks diff --git a/aquilon/test_netbox2aquilon.py b/aquilon/test_netbox2aquilon.py index 5fab6e4..99ef67e 100644 --- a/aquilon/test_netbox2aquilon.py +++ b/aquilon/test_netbox2aquilon.py @@ -301,3 +301,40 @@ def test__undo_cmds(): ] assert test_obj._undo_cmds(cmds_forward) == cmds_reverse + + +def test__netbox_copy_vm_disks(mocker): + test_obj = Netbox2Aquilon() + + test_obj.get_disks_from_device = mocker.MagicMock(return_value=deepcopy(FAKE.DISKS_VIRTUAL)) + + fake_device = FAKE.DEVICE_VIRTUAL + fake_device.aq_machine_name = 'netboxvm-243' + + cmds = test_obj._netbox_copy_vm_disks(fake_device) + + assert cmds == [ + [ + 'add_disk', + '--machine', 'netboxvm-243', + '--disk', 'sda', + '--controller', 'sata', + '--size', '40', + '--boot', + ], + [ + 'add_disk', + '--machine', 'netboxvm-243', + '--disk', 'sdb', + '--controller', 'sata', + '--size', '100', + ], + [ + 'add_disk', + '--machine', 'netboxvm-243', + '--disk', 'sdc', + '--controller', 'sata', + '--size', '100', + '--comments', '"/opt"', + ], + ] diff --git a/aquilon/testdata/__init__.py b/aquilon/testdata/__init__.py index 097866f..fb2fa5a 100644 --- a/aquilon/testdata/__init__.py +++ b/aquilon/testdata/__init__.py @@ -21,6 +21,7 @@ def load_data(): files = { 'device_physical': pynetbox.models.dcim.Devices, 'device_virtual': pynetbox.models.virtualization.VirtualMachines, + 'disks_virtual': pynetbox.core.response.Record, 'interfaces_physical': pynetbox.models.dcim.Interfaces, 'interfaces_physical_lags': pynetbox.models.dcim.Interfaces, 'interfaces_virtual': pynetbox.core.response.Record, diff --git a/aquilon/testdata/disks_virtual.json b/aquilon/testdata/disks_virtual.json new file mode 100644 index 0000000..90dc111 --- /dev/null +++ b/aquilon/testdata/disks_virtual.json @@ -0,0 +1,56 @@ +[ + { + "id": 2, + "url": "https://netbox.example.org/api/virtualization/virtual-disks/2/?format=json", + "display": "sda", + "virtual_machine": { + "id": 243, + "url": "https://netbox.example.org/api/virtualization/virtual-machines/243/?format=json", + "display": "vm.example.org", + "name": "vm.example.org" + }, + "name": "sda", + "description": "", + "size": 40, + "tags": [], + "custom_fields": {}, + "created": "2025-06-02T13:43:40.552134Z", + "last_updated": "2025-06-02T13:43:40.552152Z" + }, + { + "id": 3, + "url": "https://netbox.example.org/api/virtualization/virtual-disks/3/?format=json", + "display": "sdb", + "virtual_machine": { + "id": 243, + "url": "https://netbox.example.org/api/virtualization/virtual-machines/243/?format=json", + "display": "vm.example.org", + "name": "vm.example.org" + }, + "name": "sdb", + "description": "", + "size": 100, + "tags": [], + "custom_fields": {}, + "created": "2025-06-02T13:43:49.494852Z", + "last_updated": "2025-06-02T13:43:49.494873Z" + }, + { + "id": 4, + "url": "https://netbox.example.org/api/virtualization/virtual-disks/4/?format=json", + "display": "sdc", + "virtual_machine": { + "id": 243, + "url": "https://netbox.example.org/api/virtualization/virtual-machines/243/?format=json", + "display": "vm.example.org", + "name": "vm.example.org" + }, + "name": "sdc", + "description": "/opt", + "size": 100, + "tags": [], + "custom_fields": {}, + "created": "2025-06-02T13:44:01.016349Z", + "last_updated": "2025-06-02T13:44:01.016370Z" + } +] diff --git a/requirements.txt b/requirements.txt index d8ab419..e3bf366 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ requests>=2.27.1 -pynetbox==6.6.2 +pynetbox==6.6.2; python_version < "3.9" +pynetbox==7.3.4; python_version >= "3.9" coloredlogs>=7.3