various improvements
This commit is contained in:
		@@ -78,8 +78,8 @@ class GuestAgent:
 | 
			
		||||
        except GuestAgentError:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def available_commands(self) -> set[str]:
 | 
			
		||||
        """Return set of available guest agent commands."""
 | 
			
		||||
    def get_supported_commands(self) -> set[str]:
 | 
			
		||||
        """Return set of supported guest agent commands."""
 | 
			
		||||
        output = self.execute({'execute': 'guest-info', 'arguments': {}})
 | 
			
		||||
        return {
 | 
			
		||||
            cmd['name']
 | 
			
		||||
@@ -94,8 +94,9 @@ class GuestAgent:
 | 
			
		||||
        :param commands: List of required commands
 | 
			
		||||
        :raise: GuestAgentCommandNotSupportedError
 | 
			
		||||
        """
 | 
			
		||||
        supported = self.get_supported_commands()
 | 
			
		||||
        for command in commands:
 | 
			
		||||
            if command not in self.available_commands():
 | 
			
		||||
            if command not in supported:
 | 
			
		||||
                raise GuestAgentCommandNotSupportedError(command)
 | 
			
		||||
 | 
			
		||||
    def guest_exec(  # noqa: PLR0913
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ from compute.exceptions import (
 | 
			
		||||
    GuestAgentCommandNotSupportedError,
 | 
			
		||||
    InstanceError,
 | 
			
		||||
)
 | 
			
		||||
from compute.storage import DiskConfig
 | 
			
		||||
from compute.utils import units
 | 
			
		||||
 | 
			
		||||
from .guest_agent import GuestAgent
 | 
			
		||||
@@ -181,7 +182,7 @@ class InstanceInfo(NamedTuple):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DeviceConfig:
 | 
			
		||||
    """Abstract device description class."""
 | 
			
		||||
    """Abstract device config class."""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Instance:
 | 
			
		||||
@@ -485,6 +486,11 @@ class Instance:
 | 
			
		||||
            msg = f'Cannot set memory for instance={self.name} {memory=}: {e}'
 | 
			
		||||
            raise InstanceError(msg) from e
 | 
			
		||||
 | 
			
		||||
    def _get_disk_by_target(self, target: str) -> etree.Element:
 | 
			
		||||
        xml = etree.fromstring(self.dump_xml())  # noqa: S320
 | 
			
		||||
        child = xml.xpath(f'/domain/devices/disk/target[@dev="{target}"]')
 | 
			
		||||
        return child[0].getparent() if child else None
 | 
			
		||||
 | 
			
		||||
    def attach_device(
 | 
			
		||||
        self, device: 'DeviceConfig', *, live: bool = False
 | 
			
		||||
    ) -> None:
 | 
			
		||||
@@ -501,6 +507,13 @@ class Instance:
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
			
		||||
        if isinstance(device, DiskConfig):  # noqa: SIM102
 | 
			
		||||
            if self._get_disk_by_target(device.target):
 | 
			
		||||
                log.warning(
 | 
			
		||||
                    "Volume with target '%s' is already attached",
 | 
			
		||||
                    device.target,
 | 
			
		||||
                )
 | 
			
		||||
                return
 | 
			
		||||
        self.domain.attachDeviceFlags(device.to_xml(), flags=flags)
 | 
			
		||||
 | 
			
		||||
    def detach_device(
 | 
			
		||||
@@ -519,8 +532,48 @@ class Instance:
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
			
		||||
        if isinstance(device, DiskConfig):  # noqa: SIM102
 | 
			
		||||
            if self._get_disk_by_target(device.target) is None:
 | 
			
		||||
                log.warning(
 | 
			
		||||
                    "Volume with target '%s' is already detached",
 | 
			
		||||
                    device.target,
 | 
			
		||||
                )
 | 
			
		||||
                return
 | 
			
		||||
        self.domain.detachDeviceFlags(device.to_xml(), flags=flags)
 | 
			
		||||
 | 
			
		||||
    def detach_disk(self, name: str) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Detach disk device by target name.
 | 
			
		||||
 | 
			
		||||
        There is no ``attach_disk()`` method. Use :method:`attach_device`
 | 
			
		||||
        with :class:`DiskConfig` as parameter.
 | 
			
		||||
 | 
			
		||||
        :param name: Disk name e.g. 'vda', 'sda', etc. This name may
 | 
			
		||||
            not match the name of the disk inside the guest OS.
 | 
			
		||||
        """
 | 
			
		||||
        xml = self._get_disk_by_target(name)
 | 
			
		||||
        if xml is None:
 | 
			
		||||
            log.warning(
 | 
			
		||||
                "Volume with target '%s' is already detached",
 | 
			
		||||
                name,
 | 
			
		||||
            )
 | 
			
		||||
            return
 | 
			
		||||
        disk_params = {
 | 
			
		||||
            'disk_type': xml.get('type'),
 | 
			
		||||
            'source': xml.find('source').get('file'),
 | 
			
		||||
            'target': xml.find('target').get('dev'),
 | 
			
		||||
            'readonly': False if xml.find('readonly') is None else True,  # noqa: SIM211
 | 
			
		||||
        }
 | 
			
		||||
        for param in disk_params:
 | 
			
		||||
            if disk_params[param] is None:
 | 
			
		||||
                msg = (
 | 
			
		||||
                    f"Cannot detach volume with target '{name}': "
 | 
			
		||||
                    f"parameter '{param}' is not defined in libvirt XML "
 | 
			
		||||
                    'config on host.'
 | 
			
		||||
                )
 | 
			
		||||
                raise InstanceError(msg)
 | 
			
		||||
        self.detach_device(DiskConfig(**disk_params), live=True)
 | 
			
		||||
 | 
			
		||||
    def resize_volume(
 | 
			
		||||
        self, name: str, capacity: int, unit: units.DataUnit
 | 
			
		||||
    ) -> None:
 | 
			
		||||
@@ -573,7 +626,9 @@ class Instance:
 | 
			
		||||
        """
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    def set_user_password(self, user: str, password: str) -> None:
 | 
			
		||||
    def set_user_password(
 | 
			
		||||
        self, user: str, password: str, *, encrypted: bool = False
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Set new user password in guest OS.
 | 
			
		||||
 | 
			
		||||
@@ -581,8 +636,16 @@ class Instance:
 | 
			
		||||
 | 
			
		||||
        :param user: Username.
 | 
			
		||||
        :param password: Password.
 | 
			
		||||
        :param encrypted: Set it to True if password is already encrypted.
 | 
			
		||||
            Right encryption method depends on guest OS.
 | 
			
		||||
        """
 | 
			
		||||
        self.domain.setUserPassword(user, password)
 | 
			
		||||
        if not self.guest_agent.is_available():
 | 
			
		||||
            raise InstanceError(
 | 
			
		||||
                'Cannot change password: guest agent is unavailable'
 | 
			
		||||
            )
 | 
			
		||||
        self.guest_agent.raise_for_commands(['guest-set-user-password'])
 | 
			
		||||
        flags = libvirt.VIR_DOMAIN_PASSWORD_ENCRYPTED if encrypted else 0
 | 
			
		||||
        self.domain.setUserPassword(user, password, flags=flags)
 | 
			
		||||
 | 
			
		||||
    def dump_xml(self, *, inactive: bool = False) -> str:
 | 
			
		||||
        """Return instance XML description."""
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user