Skip to content

Commit 66f25f3

Browse files
committed
lib/vm.py: add vdis related function
Add a list of vdis object to a VM Add get_dom0_vm in host.py Add function to connect/disconnect a VDI from a VM Add a path to `Snapshot.revert()` to reset the VDIs list of the VM object that created it since a revert would change the VDI UUIDs of the VM. Signed-off-by: Damien Thenot <[email protected]>
1 parent 1d1e036 commit 66f25f3

File tree

6 files changed

+101
-11
lines changed

6 files changed

+101
-11
lines changed

lib/basevm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
from typing import TYPE_CHECKING, Any, Literal, Optional, overload
3+
from typing import TYPE_CHECKING, Any, List, Literal, Optional, overload
44

55
if TYPE_CHECKING:
66
import lib.host
@@ -59,7 +59,7 @@ def name(self) -> str:
5959
def _disk_list(self):
6060
raise NotImplementedError()
6161

62-
def vdi_uuids(self, sr_uuid=None):
62+
def vdi_uuids(self, sr_uuid: Optional[str] = None) -> List[str]:
6363
output = self._disk_list()
6464
if output == '':
6565
return []

lib/host.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def __init__(self, pool: 'lib.pool.Pool', hostname_or_ip):
6767
self.uuid = self.inventory['INSTALLATION_UUID']
6868
self.xcp_version = version.parse(self.inventory['PRODUCT_VERSION'])
6969
self.xcp_version_short = f"{self.xcp_version.major}.{self.xcp_version.minor}"
70+
self._dom0: Optional[VM] = None
7071

7172
def __str__(self):
7273
return self.hostname_or_ip
@@ -711,3 +712,20 @@ def enable_hsts_header(self):
711712
def disable_hsts_header(self):
712713
self.ssh(['rm', '-f', f'{XAPI_CONF_DIR}/00-XCP-ng-tests-enable-hsts-header.conf'])
713714
self.restart_toolstack(verify=True)
715+
716+
def get_dom0_uuid(self):
717+
return self.inventory["CONTROL_DOMAIN_UUID"]
718+
719+
def get_dom0_vm(self) -> VM:
720+
if not self._dom0:
721+
self._dom0 = VM(self.get_dom0_uuid(), self)
722+
return self._dom0
723+
724+
def get_sr_from_vdi_uuid(self, vdi_uuid: str) -> Optional[SR]:
725+
sr_uuid = self.xe("vdi-param-get", {
726+
"param-name": "sr-uuid",
727+
"uuid": vdi_uuid,
728+
})
729+
if not sr_uuid:
730+
return None
731+
return SR(sr_uuid, self.pool)

lib/snapshot.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@
22

33
from lib.basevm import BaseVM
44

5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from lib.host import Host
9+
from lib.vm import VM
10+
11+
512
class Snapshot(BaseVM):
13+
def __init__(self, uuid: str, host: "Host", vm: "VM"):
14+
self.basevm = vm
15+
super(Snapshot, self).__init__(uuid, host)
16+
617
def _disk_list(self):
718
return self.host.xe('snapshot-disk-list', {'uuid': self.uuid, 'vbd-params': ''},
819
minimal=True)
@@ -21,3 +32,4 @@ def exists(self):
2132
def revert(self):
2233
logging.info("Revert to snapshot %s", self.uuid)
2334
self.host.xe('snapshot-revert', {'uuid': self.uuid})
35+
self.basevm.create_vdis_list() # We reset the base VM object VDIs list because it changed following the revert

lib/sr.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import logging
22
import time
3-
from typing import Optional
43

54
import lib.commands as commands
6-
from lib.common import prefix_object_name, safe_split, strtobool, wait_for, wait_for_not
5+
from lib.common import (
6+
prefix_object_name,
7+
safe_split,
8+
strtobool,
9+
wait_for,
10+
wait_for_not,
11+
)
712
from lib.vdi import VDI
813

14+
from typing import Optional
15+
916
class SR:
1017
def __init__(self, uuid, pool):
1118
self.uuid = uuid

lib/vdi.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ def __init__(self, uuid, *, host=None, sr=None):
2525
# TODO: use a different approach when migration is possible
2626
if sr is None:
2727
assert host
28-
sr_uuid = host.pool.get_vdi_sr_uuid(uuid)
29-
# avoid circular import
30-
# FIXME should get it from Host instead
31-
from lib.sr import SR
32-
self.sr = SR(sr_uuid, host.pool)
28+
self.sr = host.get_sr_from_vdi_uuid(self.uuid)
3329
else:
3430
self.sr = sr
3531

lib/vm.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
)
2020
from lib.snapshot import Snapshot
2121
from lib.vbd import VBD
22+
from lib.vdi import VDI
2223
from lib.vif import VIF
2324

2425
from typing import TYPE_CHECKING, List, Literal, Optional, Union, overload
@@ -33,6 +34,7 @@ def __init__(self, uuid: str, host: 'Host'):
3334
self.previous_host = None # previous host when migrated or being migrated
3435
self.is_windows = self.param_get('platform', 'device_id', accept_unknown_key=True) == '0002'
3536
self.is_uefi = self.param_get('HVM-boot-params', 'firmware', accept_unknown_key=True) == 'uefi'
37+
self.create_vdis_list()
3638

3739
def power_state(self) -> str:
3840
return self.param_get('power-state')
@@ -273,19 +275,74 @@ def migrate(self, target_host, sr=None, network=None):
273275

274276
self.previous_host = self.host
275277
self.host = target_host
278+
self.create_vdis_list()
276279

277280
def snapshot(self, ignore_vdis=None):
278281
logging.info("Snapshot VM")
279282
args = {'uuid': self.uuid, 'new-name-label': 'Snapshot of %s' % self.uuid}
280283
if ignore_vdis:
281284
args['ignore-vdi-uuids'] = ','.join(ignore_vdis)
282-
return Snapshot(self.host.xe('vm-snapshot', args), self.host)
285+
snap_uuid = self.host.xe('vm-snapshot', args)
286+
return Snapshot(snap_uuid, self.host, self)
283287

284288
def checkpoint(self) -> Snapshot:
285289
logging.info("Checkpoint VM")
286290
return Snapshot(self.host.xe('vm-checkpoint', {'uuid': self.uuid,
287291
'new-name-label': 'Checkpoint of %s' % self.uuid}),
288-
self.host)
292+
self.host, self)
293+
294+
def connect_vdi(self, vdi: VDI, device: str = "autodetect") -> str:
295+
logging.info(f">> Plugging VDI {vdi.uuid} on VM {self.uuid}")
296+
vbd_uuid = self.host.xe("vbd-create", {
297+
"vdi-uuid": vdi.uuid,
298+
"vm-uuid": self.uuid,
299+
"device": device,
300+
})
301+
try:
302+
self.host.xe("vbd-plug", {"uuid": vbd_uuid})
303+
except commands.SSHCommandFailed:
304+
self.host.xe("vbd-destroy", {"uuid": vbd_uuid})
305+
raise
306+
307+
self.vdis.append(vdi)
308+
309+
return vbd_uuid
310+
311+
def disconnect_vdi(self, vdi: VDI):
312+
logging.info(f"<< Unplugging VDI {vdi.uuid} from VM {self.uuid}")
313+
assert vdi in self.vdis, "VDI {vdi.uuid} not in VM {self.uuid} VDI list"
314+
vbd_uuid = self.host.xe("vbd-list", {
315+
"vdi-uuid": vdi.uuid,
316+
"vm-uuid": self.uuid
317+
}, minimal=True)
318+
try:
319+
self.host.xe("vbd-unplug", {"uuid": vbd_uuid})
320+
except commands.SSHCommandFailed as e:
321+
if e.stdout == f"The device is not currently attached\ndevice: {vbd_uuid}":
322+
logging.info(f"VBD {vbd_uuid} already unplugged")
323+
else:
324+
raise
325+
self.host.xe("vbd-destroy", {"uuid": vbd_uuid})
326+
self.vdis.remove(vdi)
327+
328+
def destroy_vdi(self, vdi_uuid: str) -> None:
329+
for vdi in self.vdis:
330+
if vdi.uuid == vdi_uuid:
331+
self.vdis.remove(vdi)
332+
super().destroy_vdi(vdi_uuid)
333+
break
334+
335+
def create_vdis_list(self) -> None:
336+
""" Used to redo the VDIs list of the VM when reverting a snapshot. """
337+
try:
338+
self.vdis = [VDI(vdi_uuid, host=self.host) for vdi_uuid in self.vdi_uuids()]
339+
except commands.SSHCommandFailed as e:
340+
# Doesn't work with Dom0 since `vm-disk-list` doesn't work on it so we create empty list
341+
if e.stdout == "Error: No matching VMs found":
342+
logging.info("Couldn't get disks list. We are Dom0. Continuing...")
343+
self.vdis = []
344+
else:
345+
raise
289346

290347
def vifs(self):
291348
_vifs = []

0 commit comments

Comments
 (0)