Skip to content

Commit 784e7c2

Browse files
committed
Implement preserve_mac_address option
Fixes #163 Signed-off-by: Justin Cinkelj <justin.cinkelj@xlab.si>
1 parent b356bbe commit 784e7c2

File tree

7 files changed

+154
-15
lines changed

7 files changed

+154
-15
lines changed

changelogs/fragments/vm_clone.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
minor_changes:
3+
- Added preserve_mac_address option to vm_clone module.
4+
(https://github.com/ScaleComputing/HyperCoreAnsibleCollection/pull/187)

plugins/module_utils/nic.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
__metaclass__ = type
1010

11-
from ..module_utils.type import NicType
1211
from ..module_utils.utils import PayloadMapper
1312
from ..module_utils import errors
1413

@@ -60,9 +59,9 @@ def __eq__(self, other):
6059
@classmethod
6160
def _handle_nic_type(cls, nic_type):
6261
if nic_type:
63-
if nic_type.upper() == NicType.INTEL_E1000:
62+
if nic_type.upper() == "INTEL_E1000":
6463
actual_nic_type = nic_type.upper() # INTEL_E1000
65-
elif nic_type.upper() == NicType.VIRTIO:
64+
elif nic_type.upper() == "VIRTIO":
6665
actual_nic_type = nic_type.lower() # virtio
6766
else:
6867
actual_nic_type = nic_type.upper() # RTL8139

plugins/module_utils/type.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,25 @@
88

99
__metaclass__ = type
1010

11-
import enum
11+
from typing import Optional
12+
13+
FROM_HYPERCORE_TO_ANSIBLE_NIC_TYPE = {
14+
None: None,
15+
"RTL8139": "RTL8139",
16+
"VIRTIO": "virtio",
17+
"INTEL_E1000": "INTEL_E1000",
18+
}
19+
FROM_ANSIBLE_TO_HYPERCORE_NIC_TYPE = {
20+
v: k for k, v in FROM_HYPERCORE_TO_ANSIBLE_NIC_TYPE.items()
21+
}
1222

1323

1424
# Maybe create enums.py or scale_enums.py and move all enum classes there? @Jure @Justin
15-
class NicType(str, enum.Enum):
16-
RTL8139 = "RTL8139"
17-
VIRTIO = "VIRTIO"
18-
INTEL_E1000 = "INTEL_E1000"
25+
class NicType:
26+
@classmethod
27+
def ansible_to_hypercore(cls, ansible_value: Optional[str]) -> Optional[str]:
28+
return FROM_ANSIBLE_TO_HYPERCORE_NIC_TYPE[ansible_value]
29+
30+
@classmethod
31+
def hypercore_to_ansible(cls, hypercore_value: str) -> Optional[str]:
32+
return FROM_HYPERCORE_TO_ANSIBLE_NIC_TYPE[hypercore_value]

plugins/module_utils/vm.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from ..module_utils.task_tag import TaskTag
3131
from ..module_utils import errors
3232
from ..module_utils.snapshot_schedule import SnapshotSchedule
33+
from ..module_utils.type import NicType
3334

3435
# FROM_ANSIBLE_TO_HYPERCORE_POWER_STATE and FROM_HYPERCORE_TO_ANSIBLE_POWER_STATE are mappings for how
3536
# states are stored in python/ansible and how are they stored in hypercore
@@ -299,7 +300,14 @@ def create_export_or_import_vm_payload(ansible_dict, cloud_init, is_export):
299300

300301
@classmethod
301302
def create_clone_vm_payload(
302-
cls, clone_name, ansible_tags, hypercore_tags, cloud_init
303+
cls,
304+
clone_name,
305+
ansible_tags,
306+
hypercore_tags,
307+
cloud_init,
308+
*,
309+
preserve_mac_address,
310+
source_nics,
303311
):
304312
data = {"template": {}}
305313
if clone_name:
@@ -313,6 +321,15 @@ def create_clone_vm_payload(
313321
data["template"]["tags"] = ",".join(hypercore_tags)
314322
if cloud_init:
315323
data["template"]["cloudInitData"] = cloud_init
324+
if preserve_mac_address:
325+
data["template"]["netDevs"] = [
326+
dict(
327+
type=NicType.ansible_to_hypercore(nic.type),
328+
macAddress=nic.mac,
329+
vlan=nic.vlan,
330+
)
331+
for nic in source_nics
332+
]
316333
return data
317334

318335
@classmethod
@@ -550,7 +567,12 @@ def export_vm(self, rest_client, ansible_dict):
550567
def clone_vm(self, rest_client, ansible_dict):
551568
cloud_init_data = VM.create_cloud_init_payload(ansible_dict)
552569
data = VM.create_clone_vm_payload(
553-
ansible_dict["vm_name"], ansible_dict["tags"], self.tags, cloud_init_data
570+
ansible_dict["vm_name"],
571+
ansible_dict["tags"],
572+
self.tags,
573+
cloud_init_data,
574+
preserve_mac_address=ansible_dict["preserve_mac_address"],
575+
source_nics=self.nics,
554576
)
555577
return rest_client.create_record(
556578
endpoint=f"/rest/v1/VirDomain/{self.uuid}/clone",

plugins/modules/vm_clone.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
required: false
4242
type: list
4343
elements: str
44+
preserve_mac_address:
45+
description: Allows keeping same MAC addresses as in original VM.
46+
type: bool
47+
default: false
48+
version_added: 1.3.0
4449
notes:
4550
- C(check_mode) is not supported.
4651
"""
@@ -127,6 +132,11 @@ def main():
127132
meta_data=dict(type="str"),
128133
),
129134
),
135+
preserve_mac_address=dict(
136+
type="bool",
137+
default=False,
138+
required=False,
139+
),
130140
),
131141
)
132142

tests/unit/plugins/module_utils/test_vm.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,32 +1355,87 @@ def test_get_vm_device_type_not_nic(self, create_module, rest_client):
13551355
class TestVMClone:
13561356
def test_create_clone_vm_payload_without_cloud_init(self):
13571357
results = VM.create_clone_vm_payload(
1358-
"clone_name", ["bla", "bla1"], ["oroginal_tag", "original_tag2"], None
1358+
"clone_name",
1359+
["bla", "bla1"],
1360+
["original_tag", "original_tag2"],
1361+
None,
1362+
preserve_mac_address=False,
1363+
source_nics=[],
13591364
)
13601365
assert results == {
13611366
"template": {
13621367
"name": "clone_name",
1363-
"tags": "oroginal_tag,original_tag2,bla,bla1",
1368+
"tags": "original_tag,original_tag2,bla,bla1",
13641369
}
13651370
}
13661371

13671372
def test_create_clone_vm_payload_with_cloud_init(self):
13681373
results = VM.create_clone_vm_payload(
13691374
"clone_name",
13701375
["bla", "bla1"],
1371-
["oroginal_tag", "original_tag2"],
1376+
["original_tag", "original_tag2"],
13721377
{"userData": "something", "metaData": "else"},
1378+
preserve_mac_address=False,
1379+
source_nics=[],
13731380
)
13741381
assert results == {
13751382
"template": {
13761383
"name": "clone_name",
1377-
"tags": "oroginal_tag,original_tag2,bla,bla1",
1384+
"tags": "original_tag,original_tag2,bla,bla1",
13781385
"cloudInitData": {"userData": "something", "metaData": "else"},
13791386
}
13801387
}
13811388

1389+
def test_create_clone_vm_payload_with__preserve_mac_address__0_nics(self):
1390+
results = VM.create_clone_vm_payload(
1391+
"clone_name",
1392+
["bla", "bla1"],
1393+
["original_tag", "original_tag2"],
1394+
{"userData": "something", "metaData": "else"},
1395+
preserve_mac_address=True,
1396+
source_nics=[],
1397+
)
1398+
assert results == {
1399+
"template": {
1400+
"name": "clone_name",
1401+
"tags": "original_tag,original_tag2,bla,bla1",
1402+
"cloudInitData": {"userData": "something", "metaData": "else"},
1403+
"netDevs": [],
1404+
}
1405+
}
1406+
1407+
def test_create_clone_vm_payload_with__preserve_mac_address__1_nics(self):
1408+
results = VM.create_clone_vm_payload(
1409+
"clone_name",
1410+
["bla", "bla1"],
1411+
["original_tag", "original_tag2"],
1412+
{"userData": "something", "metaData": "else"},
1413+
preserve_mac_address=True,
1414+
source_nics=[
1415+
Nic.from_ansible(dict(type="virtio", mac="11:00:00:00:00:10", vlan=10))
1416+
],
1417+
)
1418+
assert results == {
1419+
"template": {
1420+
"name": "clone_name",
1421+
"tags": "original_tag,original_tag2,bla,bla1",
1422+
"cloudInitData": {"userData": "something", "metaData": "else"},
1423+
"netDevs": [
1424+
{
1425+
"type": "VIRTIO",
1426+
"macAddress": "11:00:00:00:00:10",
1427+
"vlan": 10,
1428+
},
1429+
],
1430+
}
1431+
}
1432+
13821433
def test_clone_vm(self, rest_client, mocker):
1383-
ansible_dict = {"vm_name": "XLAB-test-vm-clone", "tags": None}
1434+
ansible_dict = {
1435+
"vm_name": "XLAB-test-vm-clone",
1436+
"tags": None,
1437+
"preserve_mac_address": False,
1438+
}
13841439
vm_dict = {
13851440
"uuid": "7542f2gg-5f9a-51ff-8a91-8ceahgf47ghg",
13861441
"nodeUUID": "",

tests/unit/plugins/modules/test_vm_clone.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def test_run_when_VM_cloned(self, rest_client, create_module, mocker):
144144
vm_name="XLAB-test-vm-clone",
145145
source_vm_name="XLAB-test-vm",
146146
tags=None,
147+
preserve_mac_address=False,
147148
)
148149
)
149150
rest_client.get_record.side_effect = [None, None, {}, {"state": "COMPLETE"}]
@@ -178,6 +179,40 @@ def test_run_when_VM_cloned_with_tag_and_cloud_init(
178179
"user_data": "valid yaml",
179180
"meta_data": "valid yaml aswell",
180181
},
182+
preserve_mac_address=False,
183+
)
184+
)
185+
rest_client.get_record.side_effect = [None, None, {}, {"state": "COMPLETE"}]
186+
rest_client.create_record.return_value = {"taskTag": "1234"}
187+
rest_client.list_records.side_effect = [[], [self._get_empty_vm()]]
188+
mocker.patch(
189+
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
190+
).return_value = None
191+
mocker.patch(
192+
"ansible_collections.scale_computing.hypercore.plugins.module_utils.vm.SnapshotSchedule.get_snapshot_schedule"
193+
).return_value = None
194+
results = vm_clone.run(module, rest_client)
195+
assert results == (
196+
True,
197+
"Virtual machine - XLAB-test-vm - cloning complete to - XLAB-test-vm-clone.",
198+
)
199+
200+
def test_run_with_preserve_mac_address(self, rest_client, create_module, mocker):
201+
module = create_module(
202+
params=dict(
203+
cluster_instance=dict(
204+
host="https://0.0.0.0",
205+
username="admin",
206+
password="admin",
207+
),
208+
vm_name="XLAB-test-vm-clone",
209+
source_vm_name="XLAB-test-vm",
210+
tags="bla,bla1",
211+
cloud_init={
212+
"user_data": "valid yaml",
213+
"meta_data": "valid yaml aswell",
214+
},
215+
preserve_mac_address=True,
181216
)
182217
)
183218
rest_client.get_record.side_effect = [None, None, {}, {"state": "COMPLETE"}]

0 commit comments

Comments
 (0)