v0.1.0-dev4
This commit is contained in:
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -48,5 +48,5 @@ test-build: build-deb
 | 
				
			|||||||
	scp packaging/build/compute*.deb vm:~
 | 
						scp packaging/build/compute*.deb vm:~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
upload-docs: docs-versions
 | 
					upload-docs: docs-versions
 | 
				
			||||||
	ssh root@hitomi 'rm -rf /srv/http/nixhacks.net/hstack/*'
 | 
						ssh root@hitomi 'rm -rf /srv/http/nixhacks.net/hstack/compute/*'
 | 
				
			||||||
	scp -r $(DOCS_BUILDDIR)/* root@hitomi:/srv/http/nixhacks.net/hstack/
 | 
						scp -r $(DOCS_BUILDDIR)/* root@hitomi:/srv/http/nixhacks.net/hstack/compute/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							@@ -4,7 +4,8 @@ Compute instances management library.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Docs
 | 
					## Docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Run `make serve-docs`. See [Development](#development) below.
 | 
					Documantation is available [here](https://nixhacks.net/hstack/compute/master/index.html).
 | 
				
			||||||
 | 
					To build actual docs run `make serve-docs`. See [Development](#development) below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Roadmap
 | 
					## Roadmap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -41,6 +42,7 @@ Run `make serve-docs`. See [Development](#development) below.
 | 
				
			|||||||
- [ ] LXC
 | 
					- [ ] LXC
 | 
				
			||||||
- [ ] Attaching CDROM from sources: block, (http|https|ftp|ftps|tftp)://
 | 
					- [ ] Attaching CDROM from sources: block, (http|https|ftp|ftps|tftp)://
 | 
				
			||||||
- [ ] Instance clones (thin, fat)
 | 
					- [ ] Instance clones (thin, fat)
 | 
				
			||||||
 | 
					- [ ] MicroVM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Development
 | 
					## Development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,7 +54,7 @@ Install [poetry](https://python-poetry.org/), clone this repository and run:
 | 
				
			|||||||
poetry install --with dev --with docs
 | 
					poetry install --with dev --with docs
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Build Debian package
 | 
					## Build Debian package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Install Docker first, then run:
 | 
					Install Docker first, then run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,11 +64,11 @@ make build-deb
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
`compute` and `compute-doc` packages will built. See packaging/build directory.
 | 
					`compute` and `compute-doc` packages will built. See packaging/build directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Installation
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
See [Installation](https://nixhacks.net/hstack/compute/master/installation.html).
 | 
					See [Installation](https://nixhacks.net/hstack/compute/master/installation.html).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Basic usage
 | 
					## Basic usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To get help run:
 | 
					To get help run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
"""Compute instances management library."""
 | 
					"""Compute instances management library."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = '0.1.0-dev3'
 | 
					__version__ = '0.1.0-dev4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .config import Config
 | 
					from .config import Config
 | 
				
			||||||
from .instance import CloudInit, Instance, InstanceConfig, InstanceSchema
 | 
					from .instance import CloudInit, Instance, InstanceConfig, InstanceSchema
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,7 +55,7 @@ def init(session: Session, args: argparse.Namespace) -> None:
 | 
				
			|||||||
    capabilities = session.get_capabilities()
 | 
					    capabilities = session.get_capabilities()
 | 
				
			||||||
    node_info = session.get_node_info()
 | 
					    node_info = session.get_node_info()
 | 
				
			||||||
    base_instance_config = {
 | 
					    base_instance_config = {
 | 
				
			||||||
        'name': str(uuid.uuid4()),
 | 
					        'name': str(uuid.uuid4()).split('-')[0],
 | 
				
			||||||
        'title': None,
 | 
					        'title': None,
 | 
				
			||||||
        'description': None,
 | 
					        'description': None,
 | 
				
			||||||
        'arch': capabilities.arch,
 | 
					        'arch': capabilities.arch,
 | 
				
			||||||
@@ -70,16 +70,28 @@ def init(session: Session, args: argparse.Namespace) -> None:
 | 
				
			|||||||
            'topology': None,
 | 
					            'topology': None,
 | 
				
			||||||
            'features': None,
 | 
					            'features': None,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        'network_interfaces': [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                'source': 'default',
 | 
					 | 
				
			||||||
                'mac': ids.random_mac(),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        ],
 | 
					 | 
				
			||||||
        'boot': {'order': ['cdrom', 'hd']},
 | 
					        'boot': {'order': ['cdrom', 'hd']},
 | 
				
			||||||
        'cloud_init': None,
 | 
					        'cloud_init': None,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    data = dictutil.override(base_instance_config, data)
 | 
					    data = dictutil.override(base_instance_config, data)
 | 
				
			||||||
 | 
					    net_default_interface = {
 | 
				
			||||||
 | 
					        'model': 'virtio',
 | 
				
			||||||
 | 
					        'source': 'default',
 | 
				
			||||||
 | 
					        'mac': ids.random_mac(),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    net_config = data.get('network', 'DEFAULT')
 | 
				
			||||||
 | 
					    if net_config == 'DEFAULT' or net_config is True:
 | 
				
			||||||
 | 
					        data['network'] = {'interfaces': [net_default_interface]}
 | 
				
			||||||
 | 
					    elif net_config is None or net_config is False:
 | 
				
			||||||
 | 
					        pass  # allow creating instance without network interfaces
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        interfaces = data['network'].get('interfaces')
 | 
				
			||||||
 | 
					        if interfaces:
 | 
				
			||||||
 | 
					            interfaces_configs = [
 | 
				
			||||||
 | 
					                dictutil.override(net_default_interface, interface)
 | 
				
			||||||
 | 
					                for interface in interfaces
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            data['network']['interfaces'] = interfaces_configs
 | 
				
			||||||
    volumes = []
 | 
					    volumes = []
 | 
				
			||||||
    targets = []
 | 
					    targets = []
 | 
				
			||||||
    for volume in data['volumes']:
 | 
					    for volume in data['volumes']:
 | 
				
			||||||
@@ -246,8 +258,8 @@ def shutdown(session: Session, args: argparse.Namespace) -> None:
 | 
				
			|||||||
        method = 'SOFT'
 | 
					        method = 'SOFT'
 | 
				
			||||||
    elif args.hard:
 | 
					    elif args.hard:
 | 
				
			||||||
        method = 'HARD'
 | 
					        method = 'HARD'
 | 
				
			||||||
    elif args.unsafe:
 | 
					    elif args.destroy:
 | 
				
			||||||
        method = 'UNSAFE'
 | 
					        method = 'DESTROY'
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        method = 'NORMAL'
 | 
					        method = 'NORMAL'
 | 
				
			||||||
    instance.shutdown(method)
 | 
					    instance.shutdown(method)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -226,7 +226,7 @@ def get_parser() -> argparse.ArgumentParser:
 | 
				
			|||||||
        '-s',
 | 
					        '-s',
 | 
				
			||||||
        '--soft',
 | 
					        '--soft',
 | 
				
			||||||
        action='store_true',
 | 
					        action='store_true',
 | 
				
			||||||
        help='normal guest OS shutdown, guest agent is used',
 | 
					        help='guest OS shutdown using guest agent',
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    shutdown_opts.add_argument(
 | 
					    shutdown_opts.add_argument(
 | 
				
			||||||
        '-n',
 | 
					        '-n',
 | 
				
			||||||
@@ -244,12 +244,12 @@ def get_parser() -> argparse.ArgumentParser:
 | 
				
			|||||||
        ),
 | 
					        ),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    shutdown_opts.add_argument(
 | 
					    shutdown_opts.add_argument(
 | 
				
			||||||
        '-u',
 | 
					        '-d',
 | 
				
			||||||
        '--unsafe',
 | 
					        '--destroy',
 | 
				
			||||||
        action='store_true',
 | 
					        action='store_true',
 | 
				
			||||||
        help=(
 | 
					        help=(
 | 
				
			||||||
            'destroy instance, this is similar to a power outage '
 | 
					            'destroy instance, this is similar to a power outage '
 | 
				
			||||||
            'and may result in data loss or corruption'
 | 
					            'and may result in data corruption'
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    shutdown.set_defaults(func=commands.shutdown)
 | 
					    shutdown.set_defaults(func=commands.shutdown)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,7 +92,7 @@ class InvalidDeviceConfigError(ComputeError):
 | 
				
			|||||||
        """Initialise InvalidDeviceConfigError."""
 | 
					        """Initialise InvalidDeviceConfigError."""
 | 
				
			||||||
        self.msg = f'Invalid device XML config: {msg}'
 | 
					        self.msg = f'Invalid device XML config: {msg}'
 | 
				
			||||||
        self.loc = f'    {xml}'
 | 
					        self.loc = f'    {xml}'
 | 
				
			||||||
        super().__init__(f'{self.msg}\n:{self.loc}')
 | 
					        super().__init__(f'{self.msg}:\n{self.loc}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InvalidDataUnitError(ValueError, ComputeError):
 | 
					class InvalidDataUnitError(ValueError, ComputeError):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -137,7 +137,7 @@ class CloudInit:
 | 
				
			|||||||
        subprocess.run(
 | 
					        subprocess.run(
 | 
				
			||||||
            ['/usr/sbin/mkfs.vfat', '-n', 'CIDATA', '-C', str(disk), '1024'],
 | 
					            ['/usr/sbin/mkfs.vfat', '-n', 'CIDATA', '-C', str(disk), '1024'],
 | 
				
			||||||
            check=True,
 | 
					            check=True,
 | 
				
			||||||
            stderr=subprocess.DEVNULL,
 | 
					            stdout=subprocess.DEVNULL,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self._write_to_disk(
 | 
					        self._write_to_disk(
 | 
				
			||||||
            disk=disk,
 | 
					            disk=disk,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -92,6 +92,8 @@ class DiskConfig(DeviceConfig):
 | 
				
			|||||||
                encoding='unicode',
 | 
					                encoding='unicode',
 | 
				
			||||||
                pretty_print=True,
 | 
					                pretty_print=True,
 | 
				
			||||||
            ).strip()
 | 
					            ).strip()
 | 
				
			||||||
 | 
					        source = xml.find('source')
 | 
				
			||||||
 | 
					        target = xml.find('target')
 | 
				
			||||||
        driver = xml.find('driver')
 | 
					        driver = xml.find('driver')
 | 
				
			||||||
        cachetype = driver.get('cache')
 | 
					        cachetype = driver.get('cache')
 | 
				
			||||||
        disk_params = {
 | 
					        disk_params = {
 | 
				
			||||||
@@ -102,14 +104,14 @@ class DiskConfig(DeviceConfig):
 | 
				
			|||||||
                type=driver.get('type'),
 | 
					                type=driver.get('type'),
 | 
				
			||||||
                **({'cache': cachetype} if cachetype else {}),
 | 
					                **({'cache': cachetype} if cachetype else {}),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            'source': xml.find('source').get('file'),
 | 
					            'source': source.get('file') if source is not None else None,
 | 
				
			||||||
            'target': xml.find('target').get('dev'),
 | 
					            'target': target.get('dev') if target is not None else None,
 | 
				
			||||||
            'bus': xml.find('target').get('bus'),
 | 
					            'bus': target.get('bus') if target is not None else None,
 | 
				
			||||||
            'is_readonly': False if xml.find('readonly') is None else True,
 | 
					            'is_readonly': False if xml.find('readonly') is None else True,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for param in disk_params:
 | 
					        for param in disk_params:
 | 
				
			||||||
            if disk_params[param] is None:
 | 
					            if disk_params[param] is None:
 | 
				
			||||||
                msg = f"missing XML tag '{param}'"
 | 
					                msg = f"missing tag '{param}'"
 | 
				
			||||||
                raise InvalidDeviceConfigError(msg, xml_str)
 | 
					                raise InvalidDeviceConfigError(msg, xml_str)
 | 
				
			||||||
            if param == 'driver':
 | 
					            if param == 'driver':
 | 
				
			||||||
                driver = disk_params[param]
 | 
					                driver = disk_params[param]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ __all__ = ['Instance', 'InstanceConfig', 'InstanceInfo']
 | 
				
			|||||||
import logging
 | 
					import logging
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
from typing import NamedTuple
 | 
					from typing import NamedTuple
 | 
				
			||||||
 | 
					from uuid import UUID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import libvirt
 | 
					import libvirt
 | 
				
			||||||
from lxml import etree
 | 
					from lxml import etree
 | 
				
			||||||
@@ -66,7 +67,7 @@ class InstanceConfig(EntityConfig):
 | 
				
			|||||||
        self.emulator = schema.emulator
 | 
					        self.emulator = schema.emulator
 | 
				
			||||||
        self.arch = schema.arch
 | 
					        self.arch = schema.arch
 | 
				
			||||||
        self.boot = schema.boot
 | 
					        self.boot = schema.boot
 | 
				
			||||||
        self.network_interfaces = schema.network_interfaces
 | 
					        self.network = schema.network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _gen_cpu_xml(self, cpu: CPUSchema) -> etree.Element:
 | 
					    def _gen_cpu_xml(self, cpu: CPUSchema) -> etree.Element:
 | 
				
			||||||
        options = {
 | 
					        options = {
 | 
				
			||||||
@@ -119,6 +120,7 @@ class InstanceConfig(EntityConfig):
 | 
				
			|||||||
        return E.interface(
 | 
					        return E.interface(
 | 
				
			||||||
            E.source(network=interface.source),
 | 
					            E.source(network=interface.source),
 | 
				
			||||||
            E.mac(address=interface.mac),
 | 
					            E.mac(address=interface.mac),
 | 
				
			||||||
 | 
					            E.model(type=interface.model),
 | 
				
			||||||
            type='network',
 | 
					            type='network',
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -165,7 +167,8 @@ class InstanceConfig(EntityConfig):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        devices = E.devices()
 | 
					        devices = E.devices()
 | 
				
			||||||
        devices.append(E.emulator(str(self.emulator)))
 | 
					        devices.append(E.emulator(str(self.emulator)))
 | 
				
			||||||
        for interface in self.network_interfaces:
 | 
					        if self.network:
 | 
				
			||||||
 | 
					            for interface in self.network.interfaces:
 | 
				
			||||||
                devices.append(self._gen_network_interface_xml(interface))
 | 
					                devices.append(self._gen_network_interface_xml(interface))
 | 
				
			||||||
        devices.append(E.graphics(type='vnc', autoport='yes'))
 | 
					        devices.append(E.graphics(type='vnc', autoport='yes'))
 | 
				
			||||||
        devices.append(E.input(type='tablet', bus='usb'))
 | 
					        devices.append(E.input(type='tablet', bus='usb'))
 | 
				
			||||||
@@ -212,18 +215,14 @@ class Instance:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def __init__(self, domain: libvirt.virDomain):
 | 
					    def __init__(self, domain: libvirt.virDomain):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Initialise Instance.
 | 
					        Initialise Compute Instance object.
 | 
				
			||||||
 | 
					 | 
				
			||||||
        :ivar libvirt.virDomain domain: domain object
 | 
					 | 
				
			||||||
        :ivar libvirt.virConnect connection: connection object
 | 
					 | 
				
			||||||
        :ivar str name: domain name
 | 
					 | 
				
			||||||
        :ivar GuestAgent guest_agent: :class:`GuestAgent` object
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param domain: libvirt domain object
 | 
					        :param domain: libvirt domain object
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self._domain = domain
 | 
					        self._domain = domain
 | 
				
			||||||
        self._connection = domain.connect()
 | 
					        self._connection = domain.connect()
 | 
				
			||||||
        self._name = domain.name()
 | 
					        self._name = domain.name()
 | 
				
			||||||
 | 
					        self._uuid = domain.UUID()
 | 
				
			||||||
        self._guest_agent = GuestAgent(domain)
 | 
					        self._guest_agent = GuestAgent(domain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
@@ -241,6 +240,11 @@ class Instance:
 | 
				
			|||||||
        """Instance name."""
 | 
					        """Instance name."""
 | 
				
			||||||
        return self._name
 | 
					        return self._name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def uuid(self) -> UUID:
 | 
				
			||||||
 | 
					        """Instance UUID."""
 | 
				
			||||||
 | 
					        return UUID(bytes=self._uuid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def guest_agent(self) -> GuestAgent:
 | 
					    def guest_agent(self) -> GuestAgent:
 | 
				
			||||||
        """:class:`GuestAgent` object."""
 | 
					        """:class:`GuestAgent` object."""
 | 
				
			||||||
@@ -287,10 +291,9 @@ class Instance:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def is_running(self) -> bool:
 | 
					    def is_running(self) -> bool:
 | 
				
			||||||
        """Return True if instance is running, else return False."""
 | 
					        """Return True if instance is running, else return False."""
 | 
				
			||||||
        if self.domain.isActive() != 1:
 | 
					        if self.domain.isActive() == 1:
 | 
				
			||||||
            # 0 - is inactive, -1 - is error
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
            return True
 | 
					            return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_autostart(self) -> bool:
 | 
					    def is_autostart(self) -> bool:
 | 
				
			||||||
        """Return True if instance autostart is enabled, else return False."""
 | 
					        """Return True if instance autostart is enabled, else return False."""
 | 
				
			||||||
@@ -318,7 +321,7 @@ class Instance:
 | 
				
			|||||||
        log.info("Starting instance '%s'", self.name)
 | 
					        log.info("Starting instance '%s'", self.name)
 | 
				
			||||||
        if self.is_running():
 | 
					        if self.is_running():
 | 
				
			||||||
            log.warning(
 | 
					            log.warning(
 | 
				
			||||||
                'Already started, nothing to do instance=%s', self.name
 | 
					                "Instance '%s' is already started, nothing to do", self.name
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -347,8 +350,8 @@ class Instance:
 | 
				
			|||||||
            to unplugging machine from power. Internally send SIGTERM to
 | 
					            to unplugging machine from power. Internally send SIGTERM to
 | 
				
			||||||
            instance process and destroy it gracefully.
 | 
					            instance process and destroy it gracefully.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        UNSAFE
 | 
					        DESTROY
 | 
				
			||||||
            Force shutdown. Internally send SIGKILL to instance process.
 | 
					            Forced shutdown. Internally send SIGKILL to instance process.
 | 
				
			||||||
            There is high data corruption risk!
 | 
					            There is high data corruption risk!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        If method is None NORMAL method will used.
 | 
					        If method is None NORMAL method will used.
 | 
				
			||||||
@@ -361,7 +364,7 @@ class Instance:
 | 
				
			|||||||
            'SOFT': libvirt.VIR_DOMAIN_SHUTDOWN_GUEST_AGENT,
 | 
					            'SOFT': libvirt.VIR_DOMAIN_SHUTDOWN_GUEST_AGENT,
 | 
				
			||||||
            'NORMAL': libvirt.VIR_DOMAIN_SHUTDOWN_DEFAULT,
 | 
					            'NORMAL': libvirt.VIR_DOMAIN_SHUTDOWN_DEFAULT,
 | 
				
			||||||
            'HARD': libvirt.VIR_DOMAIN_DESTROY_GRACEFUL,
 | 
					            'HARD': libvirt.VIR_DOMAIN_DESTROY_GRACEFUL,
 | 
				
			||||||
            'UNSAFE': libvirt.VIR_DOMAIN_DESTROY_DEFAULT,
 | 
					            'DESTROY': libvirt.VIR_DOMAIN_DESTROY_DEFAULT,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if method is None:
 | 
					        if method is None:
 | 
				
			||||||
            method = 'NORMAL'
 | 
					            method = 'NORMAL'
 | 
				
			||||||
@@ -372,11 +375,13 @@ class Instance:
 | 
				
			|||||||
        method = method.upper()
 | 
					        method = method.upper()
 | 
				
			||||||
        if method not in methods:
 | 
					        if method not in methods:
 | 
				
			||||||
            raise ValueError(f"Unsupported shutdown method: '{method}'")
 | 
					            raise ValueError(f"Unsupported shutdown method: '{method}'")
 | 
				
			||||||
 | 
					        if method == 'SOFT' and self.guest_agent.is_available() is False:
 | 
				
			||||||
 | 
					            method = 'NORMAL'
 | 
				
			||||||
        log.info("Performing instance shutdown with method '%s'", method)
 | 
					        log.info("Performing instance shutdown with method '%s'", method)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if method in ['SOFT', 'NORMAL']:
 | 
					            if method in ['SOFT', 'NORMAL']:
 | 
				
			||||||
                self.domain.shutdownFlags(flags=methods[method])
 | 
					                self.domain.shutdownFlags(flags=methods[method])
 | 
				
			||||||
            elif method in ['HARD', 'UNSAFE']:
 | 
					            elif method in ['HARD', 'DESTROY']:
 | 
				
			||||||
                self.domain.destroyFlags(flags=methods[method])
 | 
					                self.domain.destroyFlags(flags=methods[method])
 | 
				
			||||||
        except libvirt.libvirtError as e:
 | 
					        except libvirt.libvirtError as e:
 | 
				
			||||||
            raise InstanceError(
 | 
					            raise InstanceError(
 | 
				
			||||||
@@ -443,8 +448,7 @@ class Instance:
 | 
				
			|||||||
            self.domain.setAutostart(autostart)
 | 
					            self.domain.setAutostart(autostart)
 | 
				
			||||||
        except libvirt.libvirtError as e:
 | 
					        except libvirt.libvirtError as e:
 | 
				
			||||||
            raise InstanceError(
 | 
					            raise InstanceError(
 | 
				
			||||||
                f'Cannot set autostart flag for instance={self.name} '
 | 
					                f"Cannot set {autostart=} flag for instance '{self.name}': {e}"
 | 
				
			||||||
                f'{autostart=}: {e}'
 | 
					 | 
				
			||||||
            ) from e
 | 
					            ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_vcpus(self, nvcpus: int, *, live: bool = False) -> None:
 | 
					    def set_vcpus(self, nvcpus: int, *, live: bool = False) -> None:
 | 
				
			||||||
@@ -466,7 +470,7 @@ class Instance:
 | 
				
			|||||||
            raise InstanceError('vCPUs count is greather than max_vcpus')
 | 
					            raise InstanceError('vCPUs count is greather than max_vcpus')
 | 
				
			||||||
        if nvcpus == self.get_info().nproc:
 | 
					        if nvcpus == self.get_info().nproc:
 | 
				
			||||||
            log.warning(
 | 
					            log.warning(
 | 
				
			||||||
                'Instance instance=%s already have %s vCPUs, nothing to do',
 | 
					                "Instance '%s' already have %s vCPUs, nothing to do",
 | 
				
			||||||
                self.name,
 | 
					                self.name,
 | 
				
			||||||
                nvcpus,
 | 
					                nvcpus,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -492,18 +496,17 @@ class Instance:
 | 
				
			|||||||
                        self.domain.setVcpusFlags(nvcpus, flags=flags)
 | 
					                        self.domain.setVcpusFlags(nvcpus, flags=flags)
 | 
				
			||||||
                    except GuestAgentCommandNotSupportedError:
 | 
					                    except GuestAgentCommandNotSupportedError:
 | 
				
			||||||
                        log.warning(
 | 
					                        log.warning(
 | 
				
			||||||
                            'Cannot set vCPUs in guest via agent, you may '
 | 
					                            "'guest-set-vcpus' command is not supported, '"
 | 
				
			||||||
                            'need to apply changes in guest manually.'
 | 
					                            'you may need to enable CPUs in guest manually.'
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    log.warning(
 | 
					                    log.warning(
 | 
				
			||||||
                        'Cannot set vCPUs in guest OS on instance=%s. '
 | 
					                        'Guest agent is not installed or not connected, '
 | 
				
			||||||
                        'You may need to apply CPUs in guest manually.',
 | 
					                        'you may need to enable CPUs in guest manually.'
 | 
				
			||||||
                        self.name,
 | 
					 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
        except libvirt.libvirtError as e:
 | 
					        except libvirt.libvirtError as e:
 | 
				
			||||||
            raise InstanceError(
 | 
					            raise InstanceError(
 | 
				
			||||||
                f'Cannot set vCPUs for instance={self.name}: {e}'
 | 
					                f"Cannot set vCPUs for instance '{self.name}': {e}"
 | 
				
			||||||
            ) from e
 | 
					            ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_memory(self, memory: int, *, live: bool = False) -> None:
 | 
					    def set_memory(self, memory: int, *, live: bool = False) -> None:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,11 +16,11 @@
 | 
				
			|||||||
"""Compute instance related objects schemas."""
 | 
					"""Compute instance related objects schemas."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					from collections import Counter
 | 
				
			||||||
from enum import StrEnum
 | 
					from enum import StrEnum
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pydantic import ValidationError, validator
 | 
					from pydantic import validator
 | 
				
			||||||
from pydantic.error_wrappers import ErrorWrapper
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
from compute.abstract import EntityModel
 | 
					from compute.abstract import EntityModel
 | 
				
			||||||
from compute.utils.units import DataUnit
 | 
					from compute.utils.units import DataUnit
 | 
				
			||||||
@@ -74,12 +74,30 @@ class VolumeCapacitySchema(EntityModel):
 | 
				
			|||||||
    unit: DataUnit
 | 
					    unit: DataUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DiskCache(StrEnum):
 | 
				
			||||||
 | 
					    """Possible disk cache mechanisms enumeration."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NONE = 'none'
 | 
				
			||||||
 | 
					    WRITETHROUGH = 'writethrough'
 | 
				
			||||||
 | 
					    WRITEBACK = 'writeback'
 | 
				
			||||||
 | 
					    DIRECTSYNC = 'directsync'
 | 
				
			||||||
 | 
					    UNSAFE = 'unsafe'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DiskDriverSchema(EntityModel):
 | 
					class DiskDriverSchema(EntityModel):
 | 
				
			||||||
    """Virtual disk driver model."""
 | 
					    """Virtual disk driver model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    type: str  # noqa: A003
 | 
					    type: str  # noqa: A003
 | 
				
			||||||
    cache: str = 'writethrough'
 | 
					    cache: DiskCache = DiskCache.WRITETHROUGH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DiskBus(StrEnum):
 | 
				
			||||||
 | 
					    """Possible disk buses enumeration."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    VIRTIO = 'virtio'
 | 
				
			||||||
 | 
					    IDE = 'ide'
 | 
				
			||||||
 | 
					    SATA = 'sata'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VolumeSchema(EntityModel):
 | 
					class VolumeSchema(EntityModel):
 | 
				
			||||||
@@ -92,15 +110,30 @@ class VolumeSchema(EntityModel):
 | 
				
			|||||||
    source: str | None = None
 | 
					    source: str | None = None
 | 
				
			||||||
    is_readonly: bool = False
 | 
					    is_readonly: bool = False
 | 
				
			||||||
    is_system: bool = False
 | 
					    is_system: bool = False
 | 
				
			||||||
    bus: str = 'virtio'
 | 
					    bus: DiskBus = DiskBus.VIRTIO
 | 
				
			||||||
    device: str = 'disk'
 | 
					    device: str = 'disk'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NetworkAdapterModel(StrEnum):
 | 
				
			||||||
 | 
					    """Network adapter models."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    VIRTIO = 'virtio'
 | 
				
			||||||
 | 
					    E1000 = 'e1000'
 | 
				
			||||||
 | 
					    RTL8139 = 'rtl8139'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NetworkInterfaceSchema(EntityModel):
 | 
					class NetworkInterfaceSchema(EntityModel):
 | 
				
			||||||
    """Network inerface model."""
 | 
					    """Network inerface model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    source: str
 | 
					    source: str
 | 
				
			||||||
    mac: str
 | 
					    mac: str
 | 
				
			||||||
 | 
					    model: NetworkAdapterModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NetworkSchema(EntityModel):
 | 
				
			||||||
 | 
					    """Network configuration schema."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interfaces: list[NetworkInterfaceSchema]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BootOptionsSchema(EntityModel):
 | 
					class BootOptionsSchema(EntityModel):
 | 
				
			||||||
@@ -134,7 +167,7 @@ class InstanceSchema(EntityModel):
 | 
				
			|||||||
    arch: str
 | 
					    arch: str
 | 
				
			||||||
    boot: BootOptionsSchema
 | 
					    boot: BootOptionsSchema
 | 
				
			||||||
    volumes: list[VolumeSchema]
 | 
					    volumes: list[VolumeSchema]
 | 
				
			||||||
    network_interfaces: list[NetworkInterfaceSchema]
 | 
					    network: NetworkSchema | None | bool
 | 
				
			||||||
    image: str | None = None
 | 
					    image: str | None = None
 | 
				
			||||||
    cloud_init: CloudInitSchema | None = None
 | 
					    cloud_init: CloudInitSchema | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -142,7 +175,7 @@ class InstanceSchema(EntityModel):
 | 
				
			|||||||
    def _check_name(cls, value: str) -> str:  # noqa: N805
 | 
					    def _check_name(cls, value: str) -> str:  # noqa: N805
 | 
				
			||||||
        if not re.match(r'^[a-z0-9_-]+$', value):
 | 
					        if not re.match(r'^[a-z0-9_-]+$', value):
 | 
				
			||||||
            msg = (
 | 
					            msg = (
 | 
				
			||||||
                'Name can contain only lowercase letters, numbers, '
 | 
					                'Name must contain only lowercase letters, numbers, '
 | 
				
			||||||
                'minus sign and underscore.'
 | 
					                'minus sign and underscore.'
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
@@ -162,27 +195,33 @@ class InstanceSchema(EntityModel):
 | 
				
			|||||||
        if len([v for v in volumes if v.is_system is True]) != 1:
 | 
					        if len([v for v in volumes if v.is_system is True]) != 1:
 | 
				
			||||||
            msg = 'volumes list must contain one system volume'
 | 
					            msg = 'volumes list must contain one system volume'
 | 
				
			||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
        for vol in volumes:
 | 
					        index = 0
 | 
				
			||||||
            if vol.source is None and vol.capacity is None:
 | 
					        for volume in volumes:
 | 
				
			||||||
                raise ValidationError(
 | 
					            index += 1
 | 
				
			||||||
                    [
 | 
					            if volume.source is None and volume.capacity is None:
 | 
				
			||||||
                        ErrorWrapper(
 | 
					                msg = f"{index}: capacity is required if 'source' is unset"
 | 
				
			||||||
                            Exception(
 | 
					                raise ValueError(msg)
 | 
				
			||||||
                                "capacity is required if 'source' is unset"
 | 
					            if volume.is_system is True and volume.is_readonly is True:
 | 
				
			||||||
                            ),
 | 
					 | 
				
			||||||
                            loc='X.capacity',
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                    model=VolumeSchema,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            if vol.is_system is True and vol.is_readonly is True:
 | 
					 | 
				
			||||||
                msg = 'volume marked as system cannot be readonly'
 | 
					                msg = 'volume marked as system cannot be readonly'
 | 
				
			||||||
                raise ValueError(msg)
 | 
					                raise ValueError(msg)
 | 
				
			||||||
 | 
					        sources = [v.source for v in volumes if v.source is not None]
 | 
				
			||||||
 | 
					        targets = [v.target for v in volumes]
 | 
				
			||||||
 | 
					        for item in [sources, targets]:
 | 
				
			||||||
 | 
					            duplicates = Counter(item) - Counter(set(item))
 | 
				
			||||||
 | 
					            if duplicates:
 | 
				
			||||||
 | 
					                msg = f'find duplicate values: {list(duplicates)}'
 | 
				
			||||||
 | 
					                raise ValueError(msg)
 | 
				
			||||||
        return volumes
 | 
					        return volumes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @validator('network_interfaces')
 | 
					    @validator('network')
 | 
				
			||||||
    def _check_network_interfaces(cls, value: list) -> list:  # noqa: N805
 | 
					    def _check_network(
 | 
				
			||||||
        if not value:
 | 
					        cls,  # noqa: N805
 | 
				
			||||||
            msg = 'Network interfaces list must contain at least one element'
 | 
					        network: NetworkSchema | None | bool,
 | 
				
			||||||
 | 
					    ) -> NetworkSchema | None | bool:
 | 
				
			||||||
 | 
					        if network is True:
 | 
				
			||||||
 | 
					            msg = (
 | 
				
			||||||
 | 
					                "'network' cannot be True, set it to False "
 | 
				
			||||||
 | 
					                'or provide network configuration'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
        return value
 | 
					        return network
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -207,8 +207,8 @@ class Session(AbstractContextManager):
 | 
				
			|||||||
        :param volumes: List of storage volume configs. For more info
 | 
					        :param volumes: List of storage volume configs. For more info
 | 
				
			||||||
            see :class:`VolumeSchema`.
 | 
					            see :class:`VolumeSchema`.
 | 
				
			||||||
        :type volumes: list[dict]
 | 
					        :type volumes: list[dict]
 | 
				
			||||||
        :param network_interfaces: List of virtual network interfaces
 | 
					        :param network: List of virtual network interfaces configs.
 | 
				
			||||||
            configs. See :class:`NetworkInterfaceSchema` for more info.
 | 
					            See :class:`NetworkSchema` for more info.
 | 
				
			||||||
        :type network_interfaces: list[dict]
 | 
					        :type network_interfaces: list[dict]
 | 
				
			||||||
        :param cloud_init: Cloud-init configuration. See
 | 
					        :param cloud_init: Cloud-init configuration. See
 | 
				
			||||||
            :class:`CloudInitSchema` for info.
 | 
					            :class:`CloudInitSchema` for info.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -73,9 +73,9 @@ class StoragePool:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        Refresh storage pool.
 | 
					        Refresh storage pool.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param retry: If True retry pool refresh on :class:`libvirtError`
 | 
					        :param retry: If True retry pool refresh on 'pool have running
 | 
				
			||||||
            with running asynchronous jobs.
 | 
					            asynchronous jobs' error.
 | 
				
			||||||
        :param timeout: Retry timeout in secodns. Affets only if `retry`
 | 
					        :param timeout: Retry timeout in seconds. Affects only if `retry`
 | 
				
			||||||
            is True.
 | 
					            is True.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        retry_timeout = dt.now(tz=datetime.UTC) + timedelta(seconds=timeout)
 | 
					        retry_timeout = dt.now(tz=datetime.UTC) + timedelta(seconds=timeout)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
"""Tools for data units convertion."""
 | 
					"""Tools for data units convertion."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from collections.abc import Callable
 | 
				
			||||||
from enum import StrEnum
 | 
					from enum import StrEnum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from compute.exceptions import InvalidDataUnitError
 | 
					from compute.exceptions import InvalidDataUnitError
 | 
				
			||||||
@@ -28,6 +29,14 @@ class DataUnit(StrEnum):
 | 
				
			|||||||
    MIB = 'MiB'
 | 
					    MIB = 'MiB'
 | 
				
			||||||
    GIB = 'GiB'
 | 
					    GIB = 'GiB'
 | 
				
			||||||
    TIB = 'TiB'
 | 
					    TIB = 'TiB'
 | 
				
			||||||
 | 
					    KB = 'kb'
 | 
				
			||||||
 | 
					    MB = 'Mb'
 | 
				
			||||||
 | 
					    GB = 'Gb'
 | 
				
			||||||
 | 
					    TB = 'Tb'
 | 
				
			||||||
 | 
					    KBIT = 'kbit'
 | 
				
			||||||
 | 
					    MBIT = 'Mbit'
 | 
				
			||||||
 | 
					    GBIT = 'Gbit'
 | 
				
			||||||
 | 
					    TBIT = 'Tbit'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def _missing_(cls, name: str) -> 'DataUnit':
 | 
					    def _missing_(cls, name: str) -> 'DataUnit':
 | 
				
			||||||
@@ -37,17 +46,74 @@ class DataUnit(StrEnum):
 | 
				
			|||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def to_bytes(value: int, unit: DataUnit = DataUnit.BYTES) -> int:
 | 
					def validate_input(*args: str) -> Callable:
 | 
				
			||||||
    """Convert value to bytes. See :class:`DataUnit`."""
 | 
					    """Validate data units in functions input."""
 | 
				
			||||||
 | 
					    to_validate = args
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def decorator(func: Callable) -> Callable:
 | 
				
			||||||
 | 
					        def wrapper(*args: float | str, **kwargs: str) -> Callable:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
        _ = DataUnit(unit)
 | 
					                if kwargs:
 | 
				
			||||||
 | 
					                    for arg in to_validate:
 | 
				
			||||||
 | 
					                        unit = kwargs[arg]
 | 
				
			||||||
 | 
					                        DataUnit(unit)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    for arg in args[1:]:
 | 
				
			||||||
 | 
					                        unit = arg
 | 
				
			||||||
 | 
					                        DataUnit(unit)
 | 
				
			||||||
            except ValueError as e:
 | 
					            except ValueError as e:
 | 
				
			||||||
                raise InvalidDataUnitError(e, list(DataUnit)) from e
 | 
					                raise InvalidDataUnitError(e, list(DataUnit)) from e
 | 
				
			||||||
    powers = {
 | 
					            return func(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return decorator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@validate_input('unit')
 | 
				
			||||||
 | 
					def to_bytes(value: float, unit: DataUnit = DataUnit.BYTES) -> float:
 | 
				
			||||||
 | 
					    """Convert value to bytes."""
 | 
				
			||||||
 | 
					    unit = DataUnit(unit)
 | 
				
			||||||
 | 
					    basis = 2 if unit.endswith('iB') else 10
 | 
				
			||||||
 | 
					    factor = 125 if unit.endswith('bit') else 1
 | 
				
			||||||
 | 
					    power = {
 | 
				
			||||||
        DataUnit.BYTES: 0,
 | 
					        DataUnit.BYTES: 0,
 | 
				
			||||||
        DataUnit.KIB: 1,
 | 
					        DataUnit.KIB: 10,
 | 
				
			||||||
        DataUnit.MIB: 2,
 | 
					        DataUnit.MIB: 20,
 | 
				
			||||||
        DataUnit.GIB: 3,
 | 
					        DataUnit.GIB: 30,
 | 
				
			||||||
        DataUnit.TIB: 4,
 | 
					        DataUnit.TIB: 40,
 | 
				
			||||||
 | 
					        DataUnit.KB: 3,
 | 
				
			||||||
 | 
					        DataUnit.MB: 6,
 | 
				
			||||||
 | 
					        DataUnit.GB: 9,
 | 
				
			||||||
 | 
					        DataUnit.TB: 12,
 | 
				
			||||||
 | 
					        DataUnit.KBIT: 0,
 | 
				
			||||||
 | 
					        DataUnit.MBIT: 3,
 | 
				
			||||||
 | 
					        DataUnit.GBIT: 6,
 | 
				
			||||||
 | 
					        DataUnit.TBIT: 9,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return value * pow(1024, powers[unit])
 | 
					    return value * factor * pow(basis, power[unit])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@validate_input('from_unit', 'to_unit')
 | 
				
			||||||
 | 
					def convert(value: float, from_unit: DataUnit, to_unit: DataUnit) -> float:
 | 
				
			||||||
 | 
					    """Convert units."""
 | 
				
			||||||
 | 
					    value_in_bits = to_bytes(value, from_unit) * 8
 | 
				
			||||||
 | 
					    to_unit = DataUnit(to_unit)
 | 
				
			||||||
 | 
					    basis = 2 if to_unit.endswith('iB') else 10
 | 
				
			||||||
 | 
					    divisor = 1 if to_unit.endswith('bit') else 8
 | 
				
			||||||
 | 
					    power = {
 | 
				
			||||||
 | 
					        DataUnit.BYTES: 0,
 | 
				
			||||||
 | 
					        DataUnit.KIB: 10,
 | 
				
			||||||
 | 
					        DataUnit.MIB: 20,
 | 
				
			||||||
 | 
					        DataUnit.GIB: 30,
 | 
				
			||||||
 | 
					        DataUnit.TIB: 40,
 | 
				
			||||||
 | 
					        DataUnit.KB: 3,
 | 
				
			||||||
 | 
					        DataUnit.MB: 6,
 | 
				
			||||||
 | 
					        DataUnit.GB: 9,
 | 
				
			||||||
 | 
					        DataUnit.TB: 12,
 | 
				
			||||||
 | 
					        DataUnit.KBIT: 3,
 | 
				
			||||||
 | 
					        DataUnit.MBIT: 6,
 | 
				
			||||||
 | 
					        DataUnit.GBIT: 9,
 | 
				
			||||||
 | 
					        DataUnit.TBIT: 12,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return value_in_bits / divisor / pow(basis, power[to_unit])
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ sys.path.insert(0, os.path.abspath('../..'))
 | 
				
			|||||||
project = 'Compute'
 | 
					project = 'Compute'
 | 
				
			||||||
copyright = '2023, Compute Authors'
 | 
					copyright = '2023, Compute Authors'
 | 
				
			||||||
author = 'Compute Authors'
 | 
					author = 'Compute Authors'
 | 
				
			||||||
release = '0.1.0-dev3'
 | 
					release = '0.1.0-dev4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Sphinx general settings
 | 
					# Sphinx general settings
 | 
				
			||||||
extensions = [
 | 
					extensions = [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,16 +28,18 @@ Depends:
 | 
				
			|||||||
 ${misc:Depends},
 | 
					 ${misc:Depends},
 | 
				
			||||||
 qemu-system,
 | 
					 qemu-system,
 | 
				
			||||||
 qemu-utils,
 | 
					 qemu-utils,
 | 
				
			||||||
 | 
					 libvirt-daemon,
 | 
				
			||||||
 libvirt-daemon-system,
 | 
					 libvirt-daemon-system,
 | 
				
			||||||
 | 
					 libvirt-daemon-driver-qemu,
 | 
				
			||||||
 libvirt-clients,
 | 
					 libvirt-clients,
 | 
				
			||||||
 python3-libvirt,
 | 
					 python3-libvirt,
 | 
				
			||||||
 python3-lxml,
 | 
					 python3-lxml,
 | 
				
			||||||
 python3-yaml,
 | 
					 python3-yaml,
 | 
				
			||||||
 python3-pydantic,
 | 
					 python3-pydantic,
 | 
				
			||||||
 mtools,
 | 
					 mtools,
 | 
				
			||||||
 dosfstools
 | 
					 dosfstools,
 | 
				
			||||||
Recommends:
 | 
					 dnsmasq,
 | 
				
			||||||
 dnsmasq
 | 
					 dnsmasq-base
 | 
				
			||||||
Suggests:
 | 
					Suggests:
 | 
				
			||||||
 compute-doc
 | 
					 compute-doc
 | 
				
			||||||
Description: Compute instances management library (Python 3)
 | 
					Description: Compute instances management library (Python 3)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
[tool.poetry]
 | 
					[tool.poetry]
 | 
				
			||||||
name = 'compute'
 | 
					name = 'compute'
 | 
				
			||||||
version = '0.1.0-dev3'
 | 
					version = '0.1.0-dev4'
 | 
				
			||||||
description = 'Compute instances management library'
 | 
					description = 'Compute instances management library'
 | 
				
			||||||
license = 'GPL-3.0-or-later'
 | 
					license = 'GPL-3.0-or-later'
 | 
				
			||||||
authors = ['ge <ge@nixhacks.net>']
 | 
					authors = ['ge <ge@nixhacks.net>']
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user