Skip to content

Commit 39c19df

Browse files
committed
Added default logic after cloning
- cloud init's user data not has a default value - boot devices by default set as the primary Virtio disk
1 parent 34308d2 commit 39c19df

4 files changed

Lines changed: 111 additions & 16 deletions

File tree

plugins/module_utils/vm.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def __init__(
218218
power_state=None,
219219
power_action=None,
220220
nics=None, # nics represents a list of type Nic
221-
disks=None, # disks represents a list of type Nic
221+
disks=None, # disks represents a list of type Disk
222222
# boot_devices are stored as list of nics and/or disks internally.
223223
boot_devices=None,
224224
attach_guest_tools_iso=False,
@@ -423,6 +423,27 @@ def create_export_or_import_vm_payload(ansible_dict, cloud_init, is_export):
423423
payload["template"]["cloudInitData"] = cloud_init
424424
return payload
425425

426+
@staticmethod
427+
def clone_add_user_data_to_cloud_init(cloud_init):
428+
# Task 370 - the generated cloud-init should include a runcmd
429+
# to fix the grub UUID issue at first boot
430+
431+
user_data = cloud_init.get("user_data")
432+
if not user_data:
433+
return cloud_init
434+
435+
cloud_init["user_data"] = (
436+
user_data.rstrip()
437+
+ """
438+
439+
runcmd:
440+
- sed -i 's/^GRUB_DISABLE_LINUX_UUID=true/#GRUB_DISABLE_LINUX_UUID=true/' /etc/default/grub
441+
- update-grub
442+
"""
443+
)
444+
445+
return cloud_init
446+
426447
@classmethod
427448
def create_clone_vm_payload(
428449
cls,
@@ -446,6 +467,7 @@ def create_clone_vm_payload(
446467
hypercore_tags.append(tag)
447468
data["template"]["tags"] = ",".join(hypercore_tags)
448469
if cloud_init:
470+
cloud_init = cls.clone_add_user_data_to_cloud_init(cloud_init)
449471
data["template"]["cloudInitData"] = cloud_init
450472
if preserve_mac_address:
451473
data["template"]["netDevs"] = [
@@ -610,6 +632,13 @@ def find_disk(self, slot):
610632
if disk.slot == slot:
611633
return disk
612634

635+
# primary disk is the largest Virtio disk
636+
def get_primary_disk(self):
637+
virtio_disks = [disk for disk in self.disk_list if disk.disk_type == "virtio_disk"]
638+
if not virtio_disks:
639+
return None
640+
return max(virtio_disks, key=lambda disk: disk.size)
641+
613642
def post_vm_payload(self, rest_client, ansible_dict):
614643
# The rest of the keys from VM_PAYLOAD_KEYS will get set properly automatically
615644
# Cloud init will be obtained through ansible_dict - If method will be reused outside of vm module,

plugins/modules/vm_clone.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,29 @@ def run(module, rest_client):
166166
TaskTag.wait_task(rest_client, task)
167167
task_status = TaskTag.get_task_status(rest_client, task)
168168
if task_status and task_status.get("state", "") == "COMPLETE":
169-
return (
170-
True,
171-
f"Virtual machine - {module.params['source_vm_name']} - cloning complete to - {module.params['vm_name']}.",
172-
)
169+
# Get cloned VM
170+
virtual_machine_cloned_obj = VM.get_or_fail(query={"name": module.params["vm_name"]}, rest_client=rest_client)[
171+
0
172+
]
173+
# Set boot devices after cloning Issue-370 (VM starts failing as soon as another disk is attahed if boot is not specified)
174+
# By default we always set the largest Virtio disk which is the "primary disk"
175+
primary_disk = virtual_machine_cloned_obj.get_primary_disk()
176+
boot_items = [primary_disk.uuid] if primary_disk else []
177+
# previous boot order after cloning is always empty
178+
previous_boot_order = []
179+
changed = virtual_machine_cloned_obj.set_boot_devices(boot_items, module, rest_client, previous_boot_order)
180+
if changed:
181+
msg = "and boot order was set - you can change it with vm_boot_devices module"
182+
return (
183+
True,
184+
f"Virtual machine - {module.params['source_vm_name']} - cloning complete to - {module.params['vm_name']} {msg}.",
185+
)
186+
else:
187+
msg = "and boot order was not set - you can set it with vm_boot_devices module"
188+
return (
189+
True,
190+
f"Virtual machine - {module.params['source_vm_name']} - cloning complete to - {module.params['vm_name']} {msg}.",
191+
)
173192
raise errors.ScaleComputingError(
174193
f"There was a problem during cloning of {module.params['source_vm_name']}, cloning failed."
175194
)

tests/integration/targets/vm_clone/tasks/01_cleanup.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
nodeUUID: ""
1818
cause: INTERNAL
1919
register: set_to_run_task
20-
when: source_running_info.records
20+
when:
21+
- source_running_info.records is defined
22+
- source_running_info.records | length > 0
2123

2224
- name: Wait for the VM to be set to SHUTDOWN
2325
scale_computing.hypercore.task_wait:
2426
task_tag: "{{ set_to_run_task.record }}"
25-
when: set_to_run_task.record | default("")
27+
when: (set_to_run_task.record | default("") | string | length) > 0
2628

2729
- name: Delete VM XLAB-vm_clone-xyz
2830
scale_computing.hypercore.vm:

tests/unit/plugins/modules/test_vm_clone.py

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,22 @@ def test_run_when_VM_cloned(self, rest_client, create_module, mocker):
149149
source_snapshot_uuid=None,
150150
)
151151
)
152-
rest_client.get_record.side_effect = [None, None, {}, {"state": "COMPLETE"}]
152+
rest_client.get_record.side_effect = [
153+
None,
154+
None,
155+
{},
156+
{"state": "COMPLETE"},
157+
None,
158+
None,
159+
{},
160+
{"state": "COMPLETE"},
161+
]
153162
rest_client.create_record.return_value = {"taskTag": "1234"}
154-
rest_client.list_records.side_effect = [[], [self._get_empty_vm()]]
163+
rest_client.update_record.return_value = {"taskTag": "1234"}
164+
rest_client.list_records.side_effect = [[], [self._get_empty_vm()], [self._get_empty_vm()]]
165+
mocker.patch(
166+
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
167+
).return_value = None
155168
mocker.patch(
156169
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
157170
).return_value = None
@@ -161,7 +174,9 @@ def test_run_when_VM_cloned(self, rest_client, create_module, mocker):
161174
results = vm_clone.run(module, rest_client)
162175
assert results == (
163176
True,
164-
"Virtual machine - XLAB-test-vm - cloning complete to - XLAB-test-vm-clone.",
177+
"Virtual machine - XLAB-test-vm - cloning complete to - "
178+
"XLAB-test-vm-clone and boot order was not set - you can set it "
179+
"with vm_boot_devices module.",
165180
)
166181

167182
def test_run_when_VM_cloned_with_tag_and_cloud_init(self, rest_client, create_module, mocker):
@@ -184,9 +199,22 @@ def test_run_when_VM_cloned_with_tag_and_cloud_init(self, rest_client, create_mo
184199
source_snapshot_uuid=None,
185200
)
186201
)
187-
rest_client.get_record.side_effect = [None, None, {}, {"state": "COMPLETE"}]
202+
rest_client.get_record.side_effect = [
203+
None,
204+
None,
205+
{},
206+
{"state": "COMPLETE"},
207+
None,
208+
None,
209+
{},
210+
{"state": "COMPLETE"},
211+
]
188212
rest_client.create_record.return_value = {"taskTag": "1234"}
189-
rest_client.list_records.side_effect = [[], [self._get_empty_vm()]]
213+
rest_client.update_record.return_value = {"taskTag": "1234"}
214+
rest_client.list_records.side_effect = [[], [self._get_empty_vm()], [self._get_empty_vm()]]
215+
mocker.patch(
216+
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
217+
).return_value = None
190218
mocker.patch(
191219
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
192220
).return_value = None
@@ -196,7 +224,9 @@ def test_run_when_VM_cloned_with_tag_and_cloud_init(self, rest_client, create_mo
196224
results = vm_clone.run(module, rest_client)
197225
assert results == (
198226
True,
199-
"Virtual machine - XLAB-test-vm - cloning complete to - XLAB-test-vm-clone.",
227+
"Virtual machine - XLAB-test-vm - cloning complete to - "
228+
"XLAB-test-vm-clone and boot order was not set - you can set it "
229+
"with vm_boot_devices module.",
200230
)
201231

202232
def test_run_with_preserve_mac_address(self, rest_client, create_module, mocker):
@@ -219,9 +249,22 @@ def test_run_with_preserve_mac_address(self, rest_client, create_module, mocker)
219249
source_snapshot_uuid=None,
220250
)
221251
)
222-
rest_client.get_record.side_effect = [None, None, {}, {"state": "COMPLETE"}]
252+
rest_client.get_record.side_effect = [
253+
None,
254+
None,
255+
{},
256+
{"state": "COMPLETE"},
257+
None,
258+
None,
259+
{},
260+
{"state": "COMPLETE"},
261+
]
223262
rest_client.create_record.return_value = {"taskTag": "1234"}
224-
rest_client.list_records.side_effect = [[], [self._get_empty_vm()]]
263+
rest_client.update_record.return_value = {"taskTag": "1234"}
264+
rest_client.list_records.side_effect = [[], [self._get_empty_vm()], [self._get_empty_vm()]]
265+
mocker.patch(
266+
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
267+
).return_value = None
225268
mocker.patch(
226269
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
227270
).return_value = None
@@ -231,7 +274,9 @@ def test_run_with_preserve_mac_address(self, rest_client, create_module, mocker)
231274
results = vm_clone.run(module, rest_client)
232275
assert results == (
233276
True,
234-
"Virtual machine - XLAB-test-vm - cloning complete to - XLAB-test-vm-clone.",
277+
"Virtual machine - XLAB-test-vm - cloning complete to - "
278+
"XLAB-test-vm-clone and boot order was not set - you can set it "
279+
"with vm_boot_devices module.",
235280
)
236281

237282

0 commit comments

Comments
 (0)