various improvements
This commit is contained in:
		
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -11,7 +11,7 @@ build: format lint
 | 
				
			|||||||
	poetry build
 | 
						poetry build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
format:
 | 
					format:
 | 
				
			||||||
	poetry run isort --lai 2 $(SRC)
 | 
						poetry run isort $(SRC)
 | 
				
			||||||
	poetry run ruff format $(SRC)
 | 
						poetry run ruff format $(SRC)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lint:
 | 
					lint:
 | 
				
			||||||
@@ -23,7 +23,7 @@ docs:
 | 
				
			|||||||
docs-versions:
 | 
					docs-versions:
 | 
				
			||||||
	poetry run sphinx-multiversion $(DOCS_SRC) $(DOCS_BUILD)
 | 
						poetry run sphinx-multiversion $(DOCS_SRC) $(DOCS_BUILD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
serve-docs: docs-versions
 | 
					serve-docs:
 | 
				
			||||||
	poetry run sphinx-autobuild $(DOCS_SRC) $(DOCS_BUILD)
 | 
						poetry run sphinx-autobuild $(DOCS_SRC) $(DOCS_BUILD)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
clean:
 | 
					clean:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							@@ -6,7 +6,7 @@ Currently supports only QEMU/KVM based virtual machines.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
## Docs
 | 
					## Docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Run `make serve-docs`.
 | 
					Run `make serve-docs`. See [Development](#development) below.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Roadmap
 | 
					## Roadmap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,3 +39,11 @@ Run `make serve-docs`.
 | 
				
			|||||||
- [ ] Instance migrations
 | 
					- [ ] Instance migrations
 | 
				
			||||||
- [ ] HTTP API
 | 
					- [ ] HTTP API
 | 
				
			||||||
- [ ] Full functional CLI [in progress]
 | 
					- [ ] Full functional CLI [in progress]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Development
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Install [poetry](https://python-poetry.org/), clone this repository and run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					poetry install --with dev --with docs
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
"""Compute Service library."""
 | 
					"""Compute instances management library."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__version__ = '0.1.0'
 | 
					__version__ = '0.1.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +0,0 @@
 | 
				
			|||||||
import argparse
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from compute import Session
 | 
					 | 
				
			||||||
from compute.utils import identifiers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def _create_instance(session: Session, args: argparse.Namespace) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Умолчания (достать информацию из либвирта):
 | 
					 | 
				
			||||||
    - arch
 | 
					 | 
				
			||||||
    - machine
 | 
					 | 
				
			||||||
    - emulator
 | 
					 | 
				
			||||||
    - CPU
 | 
					 | 
				
			||||||
        - cpu_vendor
 | 
					 | 
				
			||||||
        - cpu_model
 | 
					 | 
				
			||||||
        - фичи
 | 
					 | 
				
			||||||
    - max_memory
 | 
					 | 
				
			||||||
    - max_vcpus
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (сегнерировать):
 | 
					 | 
				
			||||||
    - MAC адрес
 | 
					 | 
				
			||||||
    - boot_order = ('cdrom', 'hd')
 | 
					 | 
				
			||||||
    - title = ''
 | 
					 | 
				
			||||||
    - name = uuid.uuid4().hex
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    print(args)
 | 
					 | 
				
			||||||
@@ -1,12 +1,18 @@
 | 
				
			|||||||
"""Command line interface."""
 | 
					"""Command line interface."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import shlex
 | 
					import shlex
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					from collections import UserDict
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					from uuid import uuid4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import libvirt
 | 
					import libvirt
 | 
				
			||||||
 | 
					import yaml
 | 
				
			||||||
 | 
					from pydantic import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from compute import __version__
 | 
					from compute import __version__
 | 
				
			||||||
from compute.exceptions import (
 | 
					from compute.exceptions import (
 | 
				
			||||||
@@ -15,8 +21,7 @@ from compute.exceptions import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from compute.instance import GuestAgent
 | 
					from compute.instance import GuestAgent
 | 
				
			||||||
from compute.session import Session
 | 
					from compute.session import Session
 | 
				
			||||||
 | 
					from compute.utils import ids
 | 
				
			||||||
from ._create import _create_instance
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log = logging.getLogger(__name__)
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
@@ -37,41 +42,41 @@ class Table:
 | 
				
			|||||||
        """Initialise Table."""
 | 
					        """Initialise Table."""
 | 
				
			||||||
        self.whitespace = whitespace or '\t'
 | 
					        self.whitespace = whitespace or '\t'
 | 
				
			||||||
        self.header = []
 | 
					        self.header = []
 | 
				
			||||||
        self._rows = []
 | 
					        self.rows = []
 | 
				
			||||||
        self._table = ''
 | 
					        self.table = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def row(self, row: list) -> None:
 | 
					    def add_row(self, row: list) -> None:
 | 
				
			||||||
        """Add table row."""
 | 
					        """Add table row."""
 | 
				
			||||||
        self._rows.append([str(col) for col in row])
 | 
					        self.rows.append([str(col) for col in row])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def rows(self, rows: list[list]) -> None:
 | 
					    def add_rows(self, rows: list[list]) -> None:
 | 
				
			||||||
        """Add multiple rows."""
 | 
					        """Add multiple rows."""
 | 
				
			||||||
        for row in rows:
 | 
					        for row in rows:
 | 
				
			||||||
            self.row(row)
 | 
					            self.add_row(row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self) -> str:
 | 
					    def __str__(self) -> str:
 | 
				
			||||||
        """Build table and return."""
 | 
					        """Build table and return."""
 | 
				
			||||||
        widths = [max(map(len, col)) for col in zip(*self._rows, strict=True)]
 | 
					        widths = [max(map(len, col)) for col in zip(*self.rows, strict=True)]
 | 
				
			||||||
        self._rows.insert(0, [str(h).upper() for h in self.header])
 | 
					        self.rows.insert(0, [str(h).upper() for h in self.header])
 | 
				
			||||||
        for row in self._rows:
 | 
					        for row in self.rows:
 | 
				
			||||||
            self._table += self.whitespace.join(
 | 
					            self.table += self.whitespace.join(
 | 
				
			||||||
                (
 | 
					                (
 | 
				
			||||||
                    val.ljust(width)
 | 
					                    val.ljust(width)
 | 
				
			||||||
                    for val, width in zip(row, widths, strict=True)
 | 
					                    for val, width in zip(row, widths, strict=True)
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            self._table += '\n'
 | 
					            self.table += '\n'
 | 
				
			||||||
        return self._table.strip()
 | 
					        return self.table.strip()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _list_instances(session: Session) -> None:
 | 
					def _list_instances(session: Session) -> None:
 | 
				
			||||||
    table = Table()
 | 
					    table = Table()
 | 
				
			||||||
    table.header = ['NAME', 'STATE']
 | 
					    table.header = ['NAME', 'STATE']
 | 
				
			||||||
    for instance in session.list_instances():
 | 
					    for instance in session.list_instances():
 | 
				
			||||||
        table.row(
 | 
					        table.add_row(
 | 
				
			||||||
            [
 | 
					            [
 | 
				
			||||||
                instance.name,
 | 
					                instance.name,
 | 
				
			||||||
                instance.status,
 | 
					                instance.get_status(),
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    print(table)
 | 
					    print(table)
 | 
				
			||||||
@@ -113,11 +118,93 @@ def _exec_guest_agent_command(
 | 
				
			|||||||
    sys.exit(output.exitcode)
 | 
					    sys.exit(output.exitcode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _NotPresent:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Type for representing non-existent dictionary keys.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See :class:`_FillableDict`.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _FillableDict(UserDict):
 | 
				
			||||||
 | 
					    """Use :method:`fill` to add key if not present."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, data: dict):
 | 
				
			||||||
 | 
					        self.data = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fill(self, key: str, value: Any) -> None:  # noqa: ANN401
 | 
				
			||||||
 | 
					        if self.data.get(key, _NotPresent) is _NotPresent:
 | 
				
			||||||
 | 
					            self.data[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _merge_dicts(a: dict, b: dict, path: list[str] | None = None) -> dict:
 | 
				
			||||||
 | 
					    """Merge `b` into `a`. Return modified `a`."""
 | 
				
			||||||
 | 
					    if path is None:
 | 
				
			||||||
 | 
					        path = []
 | 
				
			||||||
 | 
					    for key in b:
 | 
				
			||||||
 | 
					        if key in a:
 | 
				
			||||||
 | 
					            if isinstance(a[key], dict) and isinstance(b[key], dict):
 | 
				
			||||||
 | 
					                _merge_dicts(a[key], b[key], [path + str(key)])
 | 
				
			||||||
 | 
					            elif a[key] == b[key]:
 | 
				
			||||||
 | 
					                pass  # same leaf value
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                a[key] = b[key]  # replace existing key's values
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            a[key] = b[key]
 | 
				
			||||||
 | 
					    return a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _create_instance(session: Session, file: io.TextIOWrapper) -> None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        data = _FillableDict(yaml.load(file.read(), Loader=yaml.SafeLoader))
 | 
				
			||||||
 | 
					        log.debug('Read from file: %s', data)
 | 
				
			||||||
 | 
					    except yaml.YAMLError as e:
 | 
				
			||||||
 | 
					        sys.exit(f'error: cannot parse YAML: {e}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    capabilities = session.get_capabilities()
 | 
				
			||||||
 | 
					    node_info = session.get_node_info()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data.fill('name', uuid4().hex)
 | 
				
			||||||
 | 
					    data.fill('title', None)
 | 
				
			||||||
 | 
					    data.fill('description', None)
 | 
				
			||||||
 | 
					    data.fill('arch', capabilities.arch)
 | 
				
			||||||
 | 
					    data.fill('machine', capabilities.machine)
 | 
				
			||||||
 | 
					    data.fill('emulator', capabilities.emulator)
 | 
				
			||||||
 | 
					    data.fill('max_vcpus', node_info.cpus)
 | 
				
			||||||
 | 
					    data.fill('max_memory', node_info.memory)
 | 
				
			||||||
 | 
					    data.fill('cpu', {})
 | 
				
			||||||
 | 
					    cpu = {
 | 
				
			||||||
 | 
					        'emulation_mode': 'host-passthrough',
 | 
				
			||||||
 | 
					        'model': None,
 | 
				
			||||||
 | 
					        'vendor': None,
 | 
				
			||||||
 | 
					        'topology': None,
 | 
				
			||||||
 | 
					        'features': None,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data['cpu'] = _merge_dicts(data['cpu'], cpu)
 | 
				
			||||||
 | 
					    data.fill(
 | 
				
			||||||
 | 
					        'network_interfaces',
 | 
				
			||||||
 | 
					        [{'source': 'default', 'mac': ids.random_mac()}],
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    data.fill('boot', {'order': ['cdrom', 'hd']})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        log.debug('Input data: %s', data)
 | 
				
			||||||
 | 
					        session.create_instance(**data)
 | 
				
			||||||
 | 
					    except ValidationError as e:
 | 
				
			||||||
 | 
					        for error in e.errors():
 | 
				
			||||||
 | 
					            fields = '.'.join([str(lc) for lc in error['loc']])
 | 
				
			||||||
 | 
					            print(
 | 
				
			||||||
 | 
					                f"validation error: {fields}: {error['msg']}",
 | 
				
			||||||
 | 
					                file=sys.stderr,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        sys.exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main(session: Session, args: argparse.Namespace) -> None:
 | 
					def main(session: Session, args: argparse.Namespace) -> None:
 | 
				
			||||||
    """Perform actions."""
 | 
					    """Perform actions."""
 | 
				
			||||||
    match args.command:
 | 
					    match args.command:
 | 
				
			||||||
        case 'create':
 | 
					        case 'create':
 | 
				
			||||||
            _create_instance(session, args)
 | 
					            _create_instance(session, args.file)
 | 
				
			||||||
        case 'exec':
 | 
					        case 'exec':
 | 
				
			||||||
            _exec_guest_agent_command(session, args)
 | 
					            _exec_guest_agent_command(session, args)
 | 
				
			||||||
        case 'ls':
 | 
					        case 'ls':
 | 
				
			||||||
@@ -179,30 +266,13 @@ def cli() -> None:  # noqa: PLR0915
 | 
				
			|||||||
    subparsers = root.add_subparsers(dest='command', metavar='COMMAND')
 | 
					    subparsers = root.add_subparsers(dest='command', metavar='COMMAND')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # create command
 | 
					    # create command
 | 
				
			||||||
    create = subparsers.add_parser('create', help='create compute instance')
 | 
					    create = subparsers.add_parser(
 | 
				
			||||||
    create.add_argument('image', nargs='?')
 | 
					        'create', help='create new instance from YAML config file'
 | 
				
			||||||
    create.add_argument('--name', help='instance name, used as ID')
 | 
					    )
 | 
				
			||||||
    create.add_argument('--title', help='human-understandable instance title')
 | 
					    create.add_argument(
 | 
				
			||||||
    create.add_argument('--desc', default='', help='instance description')
 | 
					        'file',
 | 
				
			||||||
    create.add_argument('--memory', type=int, help='memory in MiB')
 | 
					        type=argparse.FileType('r', encoding='UTF-8'),
 | 
				
			||||||
    create.add_argument('--max-memory', type=int, help='max memory in MiB')
 | 
					 | 
				
			||||||
    create.add_argument('--vcpus', type=int)
 | 
					 | 
				
			||||||
    create.add_argument('--max-vcpus', type=int)
 | 
					 | 
				
			||||||
    create.add_argument('--cpu-vendor')
 | 
					 | 
				
			||||||
    create.add_argument('--cpu-model')
 | 
					 | 
				
			||||||
    create.add_argument(
 | 
					 | 
				
			||||||
        '--cpu-emulation-mode',
 | 
					 | 
				
			||||||
        choices=['host-passthrough', 'host-model', 'custom'],
 | 
					 | 
				
			||||||
        default='host-passthrough',
 | 
					 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    create.add_argument('--cpu-features')
 | 
					 | 
				
			||||||
    create.add_argument('--cpu-topology')
 | 
					 | 
				
			||||||
    create.add_argument('--mahine')
 | 
					 | 
				
			||||||
    create.add_argument('--emulator')
 | 
					 | 
				
			||||||
    create.add_argument('--arch')
 | 
					 | 
				
			||||||
    create.add_argument('--boot-order')
 | 
					 | 
				
			||||||
    create.add_argument('--volume')
 | 
					 | 
				
			||||||
    create.add_argument('-f', '--file', help='create instance from YAML')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # exec subcommand
 | 
					    # exec subcommand
 | 
				
			||||||
    execute = subparsers.add_parser(
 | 
					    execute = subparsers.add_parser(
 | 
				
			||||||
@@ -303,14 +373,17 @@ def cli() -> None:  # noqa: PLR0915
 | 
				
			|||||||
    if log_level in log_levels:
 | 
					    if log_level in log_levels:
 | 
				
			||||||
        logging.basicConfig(level=log_levels[log_level])
 | 
					        logging.basicConfig(level=log_levels[log_level])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log.debug('CLI started with args: %s', args)
 | 
				
			||||||
    # Perform actions
 | 
					    # Perform actions
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        with Session(args.connect) as session:
 | 
					        with Session(args.connect) as session:
 | 
				
			||||||
            main(session, args)
 | 
					            main(session, args)
 | 
				
			||||||
    except ComputeServiceError as e:
 | 
					    except ComputeServiceError as e:
 | 
				
			||||||
        sys.exit(f'error: {e}')
 | 
					        sys.exit(f'error: {e}')
 | 
				
			||||||
    except (KeyboardInterrupt, SystemExit):
 | 
					    except KeyboardInterrupt:
 | 
				
			||||||
        sys.exit()
 | 
					        sys.exit()
 | 
				
			||||||
 | 
					    except SystemExit as e:
 | 
				
			||||||
 | 
					        sys.exit(e)
 | 
				
			||||||
    except Exception as e:  # noqa: BLE001
 | 
					    except Exception as e:  # noqa: BLE001
 | 
				
			||||||
        sys.exit(f'unexpected error {type(e)}: {e}')
 | 
					        sys.exit(f'unexpected error {type(e)}: {e}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
"""Compute Service exceptions."""
 | 
					"""Exceptions."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ComputeServiceError(Exception):
 | 
					class ComputeServiceError(Exception):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
"""Manage QEMU guest agent."""
 | 
					"""Interacting with the QEMU Guest Agent."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
@@ -19,9 +19,6 @@ from compute.exceptions import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
log = logging.getLogger(__name__)
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QEMU_TIMEOUT = 60
 | 
					 | 
				
			||||||
POLL_INTERVAL = 0.3
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GuestExecOutput(NamedTuple):
 | 
					class GuestExecOutput(NamedTuple):
 | 
				
			||||||
    """QEMU guest-exec command output."""
 | 
					    """QEMU guest-exec command output."""
 | 
				
			||||||
@@ -35,7 +32,7 @@ class GuestExecOutput(NamedTuple):
 | 
				
			|||||||
class GuestAgent:
 | 
					class GuestAgent:
 | 
				
			||||||
    """Class for interacting with QEMU guest agent."""
 | 
					    """Class for interacting with QEMU guest agent."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, domain: libvirt.virDomain, timeout: int | None = None):
 | 
					    def __init__(self, domain: libvirt.virDomain, timeout: int = 60):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Initialise GuestAgent.
 | 
					        Initialise GuestAgent.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,7 +40,7 @@ class GuestAgent:
 | 
				
			|||||||
        :param timeout: QEMU timeout
 | 
					        :param timeout: QEMU timeout
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.domain = domain
 | 
					        self.domain = domain
 | 
				
			||||||
        self.timeout = timeout or QEMU_TIMEOUT
 | 
					        self.timeout = timeout
 | 
				
			||||||
        self.flags = libvirt_qemu.VIR_DOMAIN_QEMU_MONITOR_COMMAND_DEFAULT
 | 
					        self.flags = libvirt_qemu.VIR_DOMAIN_QEMU_MONITOR_COMMAND_DEFAULT
 | 
				
			||||||
        self.last_pid = None
 | 
					        self.last_pid = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -65,9 +62,6 @@ class GuestAgent:
 | 
				
			|||||||
            return json.loads(output)
 | 
					            return json.loads(output)
 | 
				
			||||||
        except libvirt.libvirtError as e:
 | 
					        except libvirt.libvirtError as e:
 | 
				
			||||||
            if e.get_error_code() == libvirt.VIR_ERR_AGENT_UNRESPONSIVE:
 | 
					            if e.get_error_code() == libvirt.VIR_ERR_AGENT_UNRESPONSIVE:
 | 
				
			||||||
                log.exception(
 | 
					 | 
				
			||||||
                    'Guest agent is unavailable on instanse=%s', self.name
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                raise GuestAgentUnavailableError(e) from e
 | 
					                raise GuestAgentUnavailableError(e) from e
 | 
				
			||||||
            raise GuestAgentError(e) from e
 | 
					            raise GuestAgentError(e) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -95,9 +89,7 @@ class GuestAgent:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def raise_for_commands(self, commands: list[str]) -> None:
 | 
					    def raise_for_commands(self, commands: list[str]) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Check QEMU guest agent command availability.
 | 
					        Raise exception if QEMU GA command is not available.
 | 
				
			||||||
 | 
					 | 
				
			||||||
        Raise exception if command is not available.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param commands: List of required commands
 | 
					        :param commands: List of required commands
 | 
				
			||||||
        :raise: GuestAgentCommandNotSupportedError
 | 
					        :raise: GuestAgentCommandNotSupportedError
 | 
				
			||||||
@@ -164,12 +156,15 @@ class GuestAgent:
 | 
				
			|||||||
            stderr = b64decode(stderr or '').decode('utf-8')
 | 
					            stderr = b64decode(stderr or '').decode('utf-8')
 | 
				
			||||||
        return GuestExecOutput(exited, exitcode, stdout, stderr)
 | 
					        return GuestExecOutput(exited, exitcode, stdout, stderr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def guest_exec_status(self, pid: int, *, poll: bool = False) -> dict:
 | 
					    def guest_exec_status(
 | 
				
			||||||
 | 
					        self, pid: int, *, poll: bool = False, poll_interval: float = 0.3
 | 
				
			||||||
 | 
					    ) -> dict:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Execute guest-exec-status and return output.
 | 
					        Execute guest-exec-status and return output.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param pid: PID in guest
 | 
					        :param pid: PID in guest.
 | 
				
			||||||
        :param poll: If True poll command status with POLL_INTERVAL
 | 
					        :param poll: If True poll command status.
 | 
				
			||||||
 | 
					        :param poll_interval: Time between attempts to obtain command status.
 | 
				
			||||||
        :return: Command output
 | 
					        :return: Command output
 | 
				
			||||||
        :rtype: dict
 | 
					        :rtype: dict
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -185,7 +180,7 @@ class GuestAgent:
 | 
				
			|||||||
            command_status = self.execute(command)
 | 
					            command_status = self.execute(command)
 | 
				
			||||||
            if command_status['return']['exited']:
 | 
					            if command_status['return']['exited']:
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
            sleep(POLL_INTERVAL)
 | 
					            sleep(poll_interval)
 | 
				
			||||||
            now = time()
 | 
					            now = time()
 | 
				
			||||||
            if now - start_time > self.timeout:
 | 
					            if now - start_time > self.timeout:
 | 
				
			||||||
                raise GuestAgentTimeoutExceededError(self.timeout)
 | 
					                raise GuestAgentTimeoutExceededError(self.timeout)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
"""Manage compute instances."""
 | 
					"""Manage compute instances."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__all__ = ['Instance', 'InstanceConfig']
 | 
					__all__ = ['Instance', 'InstanceConfig', 'InstanceInfo']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
from dataclasses import dataclass
 | 
					from typing import NamedTuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import libvirt
 | 
					import libvirt
 | 
				
			||||||
from lxml import etree
 | 
					from lxml import etree
 | 
				
			||||||
@@ -16,14 +16,19 @@ from compute.exceptions import (
 | 
				
			|||||||
from compute.utils import units
 | 
					from compute.utils import units
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .guest_agent import GuestAgent
 | 
					from .guest_agent import GuestAgent
 | 
				
			||||||
from .schemas import CPUSchema, InstanceSchema, NetworkInterfaceSchema
 | 
					from .schemas import (
 | 
				
			||||||
 | 
					    CPUEmulationMode,
 | 
				
			||||||
 | 
					    CPUSchema,
 | 
				
			||||||
 | 
					    InstanceSchema,
 | 
				
			||||||
 | 
					    NetworkInterfaceSchema,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
log = logging.getLogger(__name__)
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InstanceConfig:
 | 
					class InstanceConfig:
 | 
				
			||||||
    """Compute instance description for libvirt."""
 | 
					    """Compute instance config builder."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, schema: InstanceSchema):
 | 
					    def __init__(self, schema: InstanceSchema):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -46,9 +51,20 @@ class InstanceConfig:
 | 
				
			|||||||
        self.network_interfaces = schema.network_interfaces
 | 
					        self.network_interfaces = schema.network_interfaces
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _gen_cpu_xml(self, cpu: CPUSchema) -> etree.Element:
 | 
					    def _gen_cpu_xml(self, cpu: CPUSchema) -> etree.Element:
 | 
				
			||||||
        xml = E.cpu(match='exact', mode=cpu.emulation_mode)
 | 
					        options = {
 | 
				
			||||||
 | 
					            'mode': cpu.emulation_mode,
 | 
				
			||||||
 | 
					            'match': 'exact',
 | 
				
			||||||
 | 
					            'check': 'partial',
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if cpu.emulation_mode == CPUEmulationMode.HOST_PASSTHROUGH:
 | 
				
			||||||
 | 
					            options['check'] = 'none'
 | 
				
			||||||
 | 
					            options['migratable'] = 'on'
 | 
				
			||||||
 | 
					        xml = E.cpu(**options)
 | 
				
			||||||
 | 
					        if cpu.model:
 | 
				
			||||||
            xml.append(E.model(cpu.model, fallback='forbid'))
 | 
					            xml.append(E.model(cpu.model, fallback='forbid'))
 | 
				
			||||||
 | 
					        if cpu.vendor:
 | 
				
			||||||
            xml.append(E.vendor(cpu.vendor))
 | 
					            xml.append(E.vendor(cpu.vendor))
 | 
				
			||||||
 | 
					        if cpu.topology:
 | 
				
			||||||
            xml.append(
 | 
					            xml.append(
 | 
				
			||||||
                E.topology(
 | 
					                E.topology(
 | 
				
			||||||
                    sockets=str(cpu.topology.sockets),
 | 
					                    sockets=str(cpu.topology.sockets),
 | 
				
			||||||
@@ -57,6 +73,7 @@ class InstanceConfig:
 | 
				
			|||||||
                    threads=str(cpu.topology.threads),
 | 
					                    threads=str(cpu.topology.threads),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					        if cpu.features:
 | 
				
			||||||
            for feature in cpu.features.require:
 | 
					            for feature in cpu.features.require:
 | 
				
			||||||
                xml.append(E.feature(policy='require', name=feature))
 | 
					                xml.append(E.feature(policy='require', name=feature))
 | 
				
			||||||
            for feature in cpu.features.disable:
 | 
					            for feature in cpu.features.disable:
 | 
				
			||||||
@@ -89,15 +106,15 @@ class InstanceConfig:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def to_xml(self) -> str:
 | 
					    def to_xml(self) -> str:
 | 
				
			||||||
        """Return XML config for libvirt."""
 | 
					        """Return XML config for libvirt."""
 | 
				
			||||||
        xml = E.domain(
 | 
					        xml = E.domain(type='kvm')
 | 
				
			||||||
            E.name(self.name),
 | 
					        xml.append(E.name(self.name))
 | 
				
			||||||
            E.title(self.title),
 | 
					        if self.title:
 | 
				
			||||||
            E.description(self.description),
 | 
					            xml.append(E.title(self.title))
 | 
				
			||||||
            E.metadata(),
 | 
					        if self.description:
 | 
				
			||||||
            E.memory(str(self.memory * 1024), unit='KiB'),
 | 
					            xml.append(E.description(self.description))
 | 
				
			||||||
            E.currentMemory(str(self.memory * 1024), unit='KiB'),
 | 
					        xml.append(E.metadata())
 | 
				
			||||||
            type='kvm',
 | 
					        xml.append(E.memory(str(self.max_memory * 1024), unit='KiB'))
 | 
				
			||||||
        )
 | 
					        xml.append(E.currentMemory(str(self.memory * 1024), unit='KiB'))
 | 
				
			||||||
        xml.append(
 | 
					        xml.append(
 | 
				
			||||||
            E.vcpu(
 | 
					            E.vcpu(
 | 
				
			||||||
                str(self.max_vcpus),
 | 
					                str(self.max_vcpus),
 | 
				
			||||||
@@ -148,8 +165,14 @@ class InstanceConfig:
 | 
				
			|||||||
        return etree.tostring(xml, encoding='unicode', pretty_print=True)
 | 
					        return etree.tostring(xml, encoding='unicode', pretty_print=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@dataclass
 | 
					class InstanceInfo(NamedTuple):
 | 
				
			||||||
class InstanceInfo:
 | 
					    """
 | 
				
			||||||
 | 
					    Store compute instance info.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Reference:
 | 
				
			||||||
 | 
					    https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainInfo
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    state: str
 | 
					    state: str
 | 
				
			||||||
    max_memory: int
 | 
					    max_memory: int
 | 
				
			||||||
    memory: int
 | 
					    memory: int
 | 
				
			||||||
@@ -193,13 +216,8 @@ class Instance:
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return states[state]
 | 
					        return states[state]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    def get_info(self) -> InstanceInfo:
 | 
				
			||||||
    def info(self) -> InstanceInfo:
 | 
					        """Return instance info."""
 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Return instance info.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainInfo
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        _info = self.domain.info()
 | 
					        _info = self.domain.info()
 | 
				
			||||||
        return InstanceInfo(
 | 
					        return InstanceInfo(
 | 
				
			||||||
            state=self._expand_instance_state(_info[0]),
 | 
					            state=self._expand_instance_state(_info[0]),
 | 
				
			||||||
@@ -209,8 +227,7 @@ class Instance:
 | 
				
			|||||||
            cputime=_info[4],
 | 
					            cputime=_info[4],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    def get_status(self) -> str:
 | 
				
			||||||
    def status(self) -> str:
 | 
					 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Return instance state: 'running', 'shutoff', etc.
 | 
					        Return instance state: 'running', 'shutoff', etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -225,7 +242,6 @@ class Instance:
 | 
				
			|||||||
            ) from e
 | 
					            ) from e
 | 
				
			||||||
        return self._expand_instance_state(state)
 | 
					        return self._expand_instance_state(state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    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:
 | 
				
			||||||
@@ -233,7 +249,6 @@ class Instance:
 | 
				
			|||||||
            return False
 | 
					            return False
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    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."""
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
@@ -244,10 +259,18 @@ class Instance:
 | 
				
			|||||||
                f'instance={self.name}: {e}'
 | 
					                f'instance={self.name}: {e}'
 | 
				
			||||||
            ) from e
 | 
					            ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_max_memory(self) -> int:
 | 
				
			||||||
 | 
					        """Maximum memory value for domain in KiB."""
 | 
				
			||||||
 | 
					        return self.domain.maxMemory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_max_vcpus(self) -> int:
 | 
				
			||||||
 | 
					        """Maximum vCPUs number for domain."""
 | 
				
			||||||
 | 
					        return self.domain.maxVcpus()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def start(self) -> None:
 | 
					    def start(self) -> None:
 | 
				
			||||||
        """Start defined instance."""
 | 
					        """Start defined instance."""
 | 
				
			||||||
        log.info('Starting instnce=%s', self.name)
 | 
					        log.info('Starting instnce=%s', self.name)
 | 
				
			||||||
        if self.is_running:
 | 
					        if self.is_running():
 | 
				
			||||||
            log.warning(
 | 
					            log.warning(
 | 
				
			||||||
                'Already started, nothing to do instance=%s', self.name
 | 
					                'Already started, nothing to do instance=%s', self.name
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -311,6 +334,15 @@ class Instance:
 | 
				
			|||||||
                f'Cannot shutdown instance={self.name} ' f'{method=}: {e}'
 | 
					                f'Cannot shutdown instance={self.name} ' f'{method=}: {e}'
 | 
				
			||||||
            ) from e
 | 
					            ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def reboot(self) -> None:
 | 
				
			||||||
 | 
					        """Send ACPI signal to guest OS to reboot. OS may ignore this."""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.domain.reboot()
 | 
				
			||||||
 | 
					        except libvirt.libvirtError as e:
 | 
				
			||||||
 | 
					            raise InstanceError(
 | 
				
			||||||
 | 
					                f'Cannot reboot instance={self.name}: {e}'
 | 
				
			||||||
 | 
					            ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reset(self) -> None:
 | 
					    def reset(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Reset instance.
 | 
					        Reset instance.
 | 
				
			||||||
@@ -331,14 +363,19 @@ class Instance:
 | 
				
			|||||||
                f'Cannot reset instance={self.name}: {e}'
 | 
					                f'Cannot reset instance={self.name}: {e}'
 | 
				
			||||||
            ) from e
 | 
					            ) from e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def reboot(self) -> None:
 | 
					    def power_reset(self) -> None:
 | 
				
			||||||
        """Send ACPI signal to guest OS to reboot. OS may ignore this."""
 | 
					        """
 | 
				
			||||||
        try:
 | 
					        Shutdown instance and start.
 | 
				
			||||||
            self.domain.reboot()
 | 
					
 | 
				
			||||||
        except libvirt.libvirtError as e:
 | 
					        By analogy with real hardware, this is a normal server shutdown,
 | 
				
			||||||
            raise InstanceError(
 | 
					        and then turning off from the power supply and turning it on again.
 | 
				
			||||||
                f'Cannot reboot instance={self.name}: {e}'
 | 
					
 | 
				
			||||||
            ) from e
 | 
					        This method is applicable in cases where there has been a
 | 
				
			||||||
 | 
					        configuration change in libvirt and you need to restart the
 | 
				
			||||||
 | 
					        instance to apply the new configuration.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.shutdown(method='NORMAL')
 | 
				
			||||||
 | 
					        self.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_autostart(self, *, enabled: bool) -> None:
 | 
					    def set_autostart(self, *, enabled: bool) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -383,7 +420,7 @@ class Instance:
 | 
				
			|||||||
            flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
					            flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
				
			||||||
            self.domain.setVcpusFlags(nvcpus, flags=flags)
 | 
					            self.domain.setVcpusFlags(nvcpus, flags=flags)
 | 
				
			||||||
            if live is True:
 | 
					            if live is True:
 | 
				
			||||||
                if not self.is_running:
 | 
					                if not self.is_running():
 | 
				
			||||||
                    log.warning(
 | 
					                    log.warning(
 | 
				
			||||||
                        'Instance is not running, changes applied in '
 | 
					                        'Instance is not running, changes applied in '
 | 
				
			||||||
                        'instance config.'
 | 
					                        'instance config.'
 | 
				
			||||||
@@ -453,7 +490,7 @@ class Instance:
 | 
				
			|||||||
        :param device: Object with device description e.g. DiskConfig
 | 
					        :param device: Object with device description e.g. DiskConfig
 | 
				
			||||||
        :param live: Affect a running instance
 | 
					        :param live: Affect a running instance
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if live and self.is_running:
 | 
					        if live and self.is_running():
 | 
				
			||||||
            flags = (
 | 
					            flags = (
 | 
				
			||||||
                libvirt.VIR_DOMAIN_AFFECT_LIVE
 | 
					                libvirt.VIR_DOMAIN_AFFECT_LIVE
 | 
				
			||||||
                | libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
					                | libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
				
			||||||
@@ -471,7 +508,7 @@ class Instance:
 | 
				
			|||||||
        :param device: Object with device description e.g. DiskConfig
 | 
					        :param device: Object with device description e.g. DiskConfig
 | 
				
			||||||
        :param live: Affect a running instance
 | 
					        :param live: Affect a running instance
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if live and self.is_running:
 | 
					        if live and self.is_running():
 | 
				
			||||||
            flags = (
 | 
					            flags = (
 | 
				
			||||||
                libvirt.VIR_DOMAIN_AFFECT_LIVE
 | 
					                libvirt.VIR_DOMAIN_AFFECT_LIVE
 | 
				
			||||||
                | libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
					                | libvirt.VIR_DOMAIN_AFFECT_CONFIG
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,11 +4,20 @@ import re
 | 
				
			|||||||
from enum import StrEnum
 | 
					from enum import StrEnum
 | 
				
			||||||
from pathlib import Path
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from pydantic import BaseModel, validator
 | 
					from pydantic import BaseModel, Extra, validator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from compute.utils.units import DataUnit
 | 
					from compute.utils.units import DataUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EntityModel(BaseModel):
 | 
				
			||||||
 | 
					    """Basic entity model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Config:
 | 
				
			||||||
 | 
					        """Do not allow extra fields."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        extra = Extra.forbid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CPUEmulationMode(StrEnum):
 | 
					class CPUEmulationMode(StrEnum):
 | 
				
			||||||
    """CPU emulation mode enumerated."""
 | 
					    """CPU emulation mode enumerated."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,7 +27,7 @@ class CPUEmulationMode(StrEnum):
 | 
				
			|||||||
    MAXIMUM = 'maximum'
 | 
					    MAXIMUM = 'maximum'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CPUTopologySchema(BaseModel):
 | 
					class CPUTopologySchema(EntityModel):
 | 
				
			||||||
    """CPU topology model."""
 | 
					    """CPU topology model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sockets: int
 | 
					    sockets: int
 | 
				
			||||||
@@ -27,67 +36,66 @@ class CPUTopologySchema(BaseModel):
 | 
				
			|||||||
    dies: int = 1
 | 
					    dies: int = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CPUFeaturesSchema(BaseModel):
 | 
					class CPUFeaturesSchema(EntityModel):
 | 
				
			||||||
    """CPU features model."""
 | 
					    """CPU features model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    require: list[str]
 | 
					    require: list[str]
 | 
				
			||||||
    disable: list[str]
 | 
					    disable: list[str]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CPUSchema(BaseModel):
 | 
					class CPUSchema(EntityModel):
 | 
				
			||||||
    """CPU model."""
 | 
					    """CPU model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    emulation_mode: CPUEmulationMode
 | 
					    emulation_mode: CPUEmulationMode
 | 
				
			||||||
    model: str
 | 
					    model: str | None
 | 
				
			||||||
    vendor: str
 | 
					    vendor: str | None
 | 
				
			||||||
    topology: CPUTopologySchema
 | 
					    topology: CPUTopologySchema | None
 | 
				
			||||||
    features: CPUFeaturesSchema
 | 
					    features: CPUFeaturesSchema | None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VolumeType(StrEnum):
 | 
					class VolumeType(StrEnum):
 | 
				
			||||||
    """Storage volume types enumeration."""
 | 
					    """Storage volume types enumeration."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FILE = 'file'
 | 
					    FILE = 'file'
 | 
				
			||||||
    NETWORK = 'network'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VolumeCapacitySchema(BaseModel):
 | 
					class VolumeCapacitySchema(EntityModel):
 | 
				
			||||||
    """Storage volume capacity field model."""
 | 
					    """Storage volume capacity field model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    value: int
 | 
					    value: int
 | 
				
			||||||
    unit: DataUnit
 | 
					    unit: DataUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VolumeSchema(BaseModel):
 | 
					class VolumeSchema(EntityModel):
 | 
				
			||||||
    """Storage volume model."""
 | 
					    """Storage volume model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    type: VolumeType  # noqa: A003
 | 
					    type: VolumeType  # noqa: A003
 | 
				
			||||||
    source: Path
 | 
					 | 
				
			||||||
    target: str
 | 
					    target: str
 | 
				
			||||||
    capacity: VolumeCapacitySchema
 | 
					    capacity: VolumeCapacitySchema
 | 
				
			||||||
    readonly: bool = False
 | 
					    source: str | None = None
 | 
				
			||||||
 | 
					    is_readonly: bool = False
 | 
				
			||||||
    is_system: bool = False
 | 
					    is_system: bool = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NetworkInterfaceSchema(BaseModel):
 | 
					class NetworkInterfaceSchema(EntityModel):
 | 
				
			||||||
    """Network inerface model."""
 | 
					    """Network inerface model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    source: str
 | 
					    source: str
 | 
				
			||||||
    mac: str
 | 
					    mac: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BootOptionsSchema(BaseModel):
 | 
					class BootOptionsSchema(EntityModel):
 | 
				
			||||||
    """Instance boot settings."""
 | 
					    """Instance boot settings."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    order: tuple
 | 
					    order: tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class InstanceSchema(BaseModel):
 | 
					class InstanceSchema(EntityModel):
 | 
				
			||||||
    """Compute instance model."""
 | 
					    """Compute instance model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    title: str
 | 
					    title: str | None
 | 
				
			||||||
    description: str
 | 
					    description: str | None
 | 
				
			||||||
    memory: int
 | 
					    memory: int
 | 
				
			||||||
    max_memory: int
 | 
					    max_memory: int
 | 
				
			||||||
    vcpus: int
 | 
					    vcpus: int
 | 
				
			||||||
@@ -96,10 +104,10 @@ class InstanceSchema(BaseModel):
 | 
				
			|||||||
    machine: str
 | 
					    machine: str
 | 
				
			||||||
    emulator: Path
 | 
					    emulator: Path
 | 
				
			||||||
    arch: str
 | 
					    arch: str
 | 
				
			||||||
    image: str
 | 
					 | 
				
			||||||
    boot: BootOptionsSchema
 | 
					    boot: BootOptionsSchema
 | 
				
			||||||
    volumes: list[VolumeSchema]
 | 
					    volumes: list[VolumeSchema]
 | 
				
			||||||
    network_interfaces: list[NetworkInterfaceSchema]
 | 
					    network_interfaces: list[NetworkInterfaceSchema]
 | 
				
			||||||
 | 
					    image: str | None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @validator('name')
 | 
					    @validator('name')
 | 
				
			||||||
    def _check_name(cls, value: str) -> str:  # noqa: N805
 | 
					    def _check_name(cls, value: str) -> str:  # noqa: N805
 | 
				
			||||||
@@ -111,12 +119,28 @@ class InstanceSchema(BaseModel):
 | 
				
			|||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
        return value
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @validator('volumes')
 | 
					    @validator('cpu')
 | 
				
			||||||
    def _check_volumes(cls, value: list) -> list:  # noqa: N805
 | 
					    def _check_topology(cls, cpu: int, values: dict) -> CPUSchema:  # noqa: N805
 | 
				
			||||||
        if len([v for v in value if v.is_system is True]) != 1:
 | 
					        topo = cpu.topology
 | 
				
			||||||
            msg = 'Volumes list must contain one system volume'
 | 
					        max_vcpus = values['max_vcpus']
 | 
				
			||||||
 | 
					        if topo and topo.sockets * topo.cores * topo.threads != max_vcpus:
 | 
				
			||||||
 | 
					            msg = f'CPU topology does not match with {max_vcpus=}'
 | 
				
			||||||
            raise ValueError(msg)
 | 
					            raise ValueError(msg)
 | 
				
			||||||
        return value
 | 
					        return cpu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @validator('volumes')
 | 
				
			||||||
 | 
					    def _check_volumes(cls, volumes: list) -> list:  # noqa: N805
 | 
				
			||||||
 | 
					        if len([v for v in volumes if v.is_system is True]) != 1:
 | 
				
			||||||
 | 
					            msg = 'volumes list must contain one system volume'
 | 
				
			||||||
 | 
					            raise ValueError(msg)
 | 
				
			||||||
 | 
					        vol_with_source = 0
 | 
				
			||||||
 | 
					        for vol in volumes:
 | 
				
			||||||
 | 
					            if vol.is_system is True and vol.is_readonly is True:
 | 
				
			||||||
 | 
					                msg = 'volume marked as system cannot be readonly'
 | 
				
			||||||
 | 
					                raise ValueError(msg)
 | 
				
			||||||
 | 
					            if vol.source is not None:
 | 
				
			||||||
 | 
					                vol_with_source += 1
 | 
				
			||||||
 | 
					        return volumes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @validator('network_interfaces')
 | 
					    @validator('network_interfaces')
 | 
				
			||||||
    def _check_network_interfaces(cls, value: list) -> list:  # noqa: N805
 | 
					    def _check_network_interfaces(cls, value: list) -> list:  # noqa: N805
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,25 @@ class Capabilities(NamedTuple):
 | 
				
			|||||||
    virt: str
 | 
					    virt: str
 | 
				
			||||||
    emulator: str
 | 
					    emulator: str
 | 
				
			||||||
    machine: str
 | 
					    machine: str
 | 
				
			||||||
 | 
					    max_vcpus: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NodeInfo(NamedTuple):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Store compute node info.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See https://libvirt.org/html/libvirt-libvirt-host.html#virNodeInfo
 | 
				
			||||||
 | 
					    NOTE: memory unit in libvirt docs is wrong! Actual unit is MiB.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arch: str
 | 
				
			||||||
 | 
					    memory: int
 | 
				
			||||||
 | 
					    cpus: int
 | 
				
			||||||
 | 
					    mhz: int
 | 
				
			||||||
 | 
					    nodes: int
 | 
				
			||||||
 | 
					    sockets: int
 | 
				
			||||||
 | 
					    cores: int
 | 
				
			||||||
 | 
					    threads: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Session(AbstractContextManager):
 | 
					class Session(AbstractContextManager):
 | 
				
			||||||
@@ -68,7 +87,21 @@ class Session(AbstractContextManager):
 | 
				
			|||||||
        """Close connection to libvirt daemon."""
 | 
					        """Close connection to libvirt daemon."""
 | 
				
			||||||
        self.connection.close()
 | 
					        self.connection.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def capabilities(self) -> Capabilities:
 | 
					    def get_node_info(self) -> NodeInfo:
 | 
				
			||||||
 | 
					        """Return information about compute node."""
 | 
				
			||||||
 | 
					        info = self.connection.getInfo()
 | 
				
			||||||
 | 
					        return NodeInfo(
 | 
				
			||||||
 | 
					            arch=info[0],
 | 
				
			||||||
 | 
					            memory=info[1],
 | 
				
			||||||
 | 
					            cpus=info[2],
 | 
				
			||||||
 | 
					            mhz=info[3],
 | 
				
			||||||
 | 
					            nodes=info[4],
 | 
				
			||||||
 | 
					            sockets=info[5],
 | 
				
			||||||
 | 
					            cores=info[6],
 | 
				
			||||||
 | 
					            threads=info[7],
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_capabilities(self) -> Capabilities:
 | 
				
			||||||
        """Return capabilities e.g. arch, virt, emulator, etc."""
 | 
					        """Return capabilities e.g. arch, virt, emulator, etc."""
 | 
				
			||||||
        prefix = '/domainCapabilities'
 | 
					        prefix = '/domainCapabilities'
 | 
				
			||||||
        caps = etree.fromstring(self.connection.getDomainCapabilities())  # noqa: S320
 | 
					        caps = etree.fromstring(self.connection.getDomainCapabilities())  # noqa: S320
 | 
				
			||||||
@@ -77,6 +110,7 @@ class Session(AbstractContextManager):
 | 
				
			|||||||
            virt=caps.xpath(f'{prefix}/domain/text()')[0],
 | 
					            virt=caps.xpath(f'{prefix}/domain/text()')[0],
 | 
				
			||||||
            emulator=caps.xpath(f'{prefix}/path/text()')[0],
 | 
					            emulator=caps.xpath(f'{prefix}/path/text()')[0],
 | 
				
			||||||
            machine=caps.xpath(f'{prefix}/machine/text()')[0],
 | 
					            machine=caps.xpath(f'{prefix}/machine/text()')[0],
 | 
				
			||||||
 | 
					            max_vcpus=int(caps.xpath(f'{prefix}/vcpu/@max')[0]),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_instance(self, **kwargs: Any) -> Instance:
 | 
					    def create_instance(self, **kwargs: Any) -> Instance:
 | 
				
			||||||
@@ -87,14 +121,35 @@ class Session(AbstractContextManager):
 | 
				
			|||||||
        :type name: str
 | 
					        :type name: str
 | 
				
			||||||
        :param title: Instance title for humans.
 | 
					        :param title: Instance title for humans.
 | 
				
			||||||
        :type title: str
 | 
					        :type title: str
 | 
				
			||||||
        :param description: Some information about instance
 | 
					        :param description: Some information about instance.
 | 
				
			||||||
        :type description: str
 | 
					        :type description: str
 | 
				
			||||||
        :param memory: Memory in MiB.
 | 
					        :param memory: Memory in MiB.
 | 
				
			||||||
        :type memory: int
 | 
					        :type memory: int
 | 
				
			||||||
        :param max_memory: Maximum memory in MiB.
 | 
					        :param max_memory: Maximum memory in MiB.
 | 
				
			||||||
        :type max_memory: int
 | 
					        :type max_memory: int
 | 
				
			||||||
 | 
					        :param vcpus: Number of vCPUs.
 | 
				
			||||||
 | 
					        :type vcpus: int
 | 
				
			||||||
 | 
					        :param max_vcpus: Maximum vCPUs.
 | 
				
			||||||
 | 
					        :type max_vcpus: int
 | 
				
			||||||
 | 
					        :param cpu: CPU configuration. See :class:`CPUSchema` for info.
 | 
				
			||||||
 | 
					        :type cpu: dict
 | 
				
			||||||
 | 
					        :param machine: QEMU emulated machine.
 | 
				
			||||||
 | 
					        :type machine: str
 | 
				
			||||||
 | 
					        :param emulator: Path to emulator.
 | 
				
			||||||
 | 
					        :type emulator: str
 | 
				
			||||||
 | 
					        :param arch: CPU architecture to virtualization.
 | 
				
			||||||
 | 
					        :type arch: str
 | 
				
			||||||
 | 
					        :param boot: Boot settings. See :class:`BootOptionsSchema`.
 | 
				
			||||||
 | 
					        :type boot: dict
 | 
				
			||||||
 | 
					        :param image: Source disk image name for system disk.
 | 
				
			||||||
 | 
					        :type image: str
 | 
				
			||||||
 | 
					        :param volumes: List of storage volume configs. For more info
 | 
				
			||||||
 | 
					            see :class:`VolumeSchema`.
 | 
				
			||||||
 | 
					        :type volumes: list[dict]
 | 
				
			||||||
 | 
					        :param network_interfaces: List of virtual network interfaces
 | 
				
			||||||
 | 
					            configs. See :class:`NetworkInterfaceSchema` for more info.
 | 
				
			||||||
 | 
					        :type network_interfaces: list[dict]
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        # TODO @ge: create instances in transaction
 | 
					 | 
				
			||||||
        data = InstanceSchema(**kwargs)
 | 
					        data = InstanceSchema(**kwargs)
 | 
				
			||||||
        config = InstanceConfig(data)
 | 
					        config = InstanceConfig(data)
 | 
				
			||||||
        log.info('Define XML...')
 | 
					        log.info('Define XML...')
 | 
				
			||||||
@@ -113,19 +168,17 @@ class Session(AbstractContextManager):
 | 
				
			|||||||
            log.info('Connecting to volumes pool...')
 | 
					            log.info('Connecting to volumes pool...')
 | 
				
			||||||
            volumes_pool = self.get_storage_pool(self.VOLUMES_POOL)
 | 
					            volumes_pool = self.get_storage_pool(self.VOLUMES_POOL)
 | 
				
			||||||
            log.info('Building volume configuration...')
 | 
					            log.info('Building volume configuration...')
 | 
				
			||||||
            # if not volume.source:
 | 
					            if not volume.source:
 | 
				
			||||||
            # В случае если пользователь передаёт source для волюма, следует
 | 
					 | 
				
			||||||
            # в либвирте делать поиск волюма по пути, а не по имени
 | 
					 | 
				
			||||||
            #    gen_vol_name
 | 
					 | 
				
			||||||
            # TODO @ge: come up with something else
 | 
					 | 
				
			||||||
                vol_name = f'{config.name}-{volume.target}-{uuid4()}.qcow2'
 | 
					                vol_name = f'{config.name}-{volume.target}-{uuid4()}.qcow2'
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                vol_name = volume.source
 | 
				
			||||||
            vol_conf = VolumeConfig(
 | 
					            vol_conf = VolumeConfig(
 | 
				
			||||||
                name=vol_name,
 | 
					                name=vol_name,
 | 
				
			||||||
                path=str(volumes_pool.path.joinpath(vol_name)),
 | 
					                path=str(volumes_pool.path.joinpath(vol_name)),
 | 
				
			||||||
                capacity=capacity,
 | 
					                capacity=capacity,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            log.info('Volume configuration is:\n %s', vol_conf.to_xml())
 | 
					            log.info('Volume configuration is:\n %s', vol_conf.to_xml())
 | 
				
			||||||
            if volume.is_system is True:
 | 
					            if volume.is_system is True and data.image:
 | 
				
			||||||
                log.info(
 | 
					                log.info(
 | 
				
			||||||
                    "Volume is marked as 'system', start cloning image..."
 | 
					                    "Volume is marked as 'system', start cloning image..."
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,8 @@ from .volume import Volume, VolumeConfig
 | 
				
			|||||||
log = logging.getLogger(__name__)
 | 
					log = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StoragePoolUsage(NamedTuple):
 | 
					class StoragePoolUsageInfo(NamedTuple):
 | 
				
			||||||
    """Storage pool usage info schema."""
 | 
					    """Storage pool usage info."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    capacity: int
 | 
					    capacity: int
 | 
				
			||||||
    allocation: int
 | 
					    allocation: int
 | 
				
			||||||
@@ -30,17 +30,17 @@ class StoragePool:
 | 
				
			|||||||
        """Initislise StoragePool."""
 | 
					        """Initislise StoragePool."""
 | 
				
			||||||
        self.pool = pool
 | 
					        self.pool = pool
 | 
				
			||||||
        self.name = pool.name()
 | 
					        self.name = pool.name()
 | 
				
			||||||
 | 
					        self.path = self._get_path()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    def _get_path(self) -> Path:
 | 
				
			||||||
    def path(self) -> Path:
 | 
					 | 
				
			||||||
        """Return storage pool path."""
 | 
					        """Return storage pool path."""
 | 
				
			||||||
        xml = etree.fromstring(self.pool.XMLDesc())  # noqa: S320
 | 
					        xml = etree.fromstring(self.pool.XMLDesc())  # noqa: S320
 | 
				
			||||||
        return Path(xml.xpath('/pool/target/path/text()')[0])
 | 
					        return Path(xml.xpath('/pool/target/path/text()')[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def usage(self) -> StoragePoolUsage:
 | 
					    def get_usage_info(self) -> StoragePoolUsageInfo:
 | 
				
			||||||
        """Return info about storage pool usage."""
 | 
					        """Return info about storage pool usage."""
 | 
				
			||||||
        xml = etree.fromstring(self.pool.XMLDesc())  # noqa: S320
 | 
					        xml = etree.fromstring(self.pool.XMLDesc())  # noqa: S320
 | 
				
			||||||
        return StoragePoolUsage(
 | 
					        return StoragePoolUsageInfo(
 | 
				
			||||||
            capacity=int(xml.xpath('/pool/capacity/text()')[0]),
 | 
					            capacity=int(xml.xpath('/pool/capacity/text()')[0]),
 | 
				
			||||||
            allocation=int(xml.xpath('/pool/allocation/text()')[0]),
 | 
					            allocation=int(xml.xpath('/pool/allocation/text()')[0]),
 | 
				
			||||||
            available=int(xml.xpath('/pool/available/text()')[0]),
 | 
					            available=int(xml.xpath('/pool/available/text()')[0]),
 | 
				
			||||||
@@ -58,7 +58,7 @@ class StoragePool:
 | 
				
			|||||||
    def create_volume(self, vol_conf: VolumeConfig) -> Volume:
 | 
					    def create_volume(self, vol_conf: VolumeConfig) -> Volume:
 | 
				
			||||||
        """Create storage volume and return Volume instance."""
 | 
					        """Create storage volume and return Volume instance."""
 | 
				
			||||||
        log.info(
 | 
					        log.info(
 | 
				
			||||||
            'Create storage volume vol=%s in pool=%s', vol_conf.name, self.pool
 | 
					            'Create storage volume vol=%s in pool=%s', vol_conf.name, self.name
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        vol = self.pool.createXML(
 | 
					        vol = self.pool.createXML(
 | 
				
			||||||
            vol_conf.to_xml(),
 | 
					            vol_conf.to_xml(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,11 +87,7 @@ class Volume:
 | 
				
			|||||||
        self.pool_name = pool.name()
 | 
					        self.pool_name = pool.name()
 | 
				
			||||||
        self.vol = vol
 | 
					        self.vol = vol
 | 
				
			||||||
        self.name = vol.name()
 | 
					        self.name = vol.name()
 | 
				
			||||||
 | 
					        self.path = Path(vol.path())
 | 
				
			||||||
    @property
 | 
					 | 
				
			||||||
    def path(self) -> Path:
 | 
					 | 
				
			||||||
        """Return path to volume."""
 | 
					 | 
				
			||||||
        return Path(self.vol.path())
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def dump_xml(self) -> str:
 | 
					    def dump_xml(self) -> str:
 | 
				
			||||||
        """Return volume XML description as string."""
 | 
					        """Return volume XML description as string."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					# Add ../../compute to path for autodoc
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
sys.path.insert(0, os.path.abspath('../../compute'))
 | 
					sys.path.insert(0, os.path.abspath('../../compute'))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								docs/source/python-api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docs/source/python-api/exceptions.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					``exceptions``
 | 
				
			||||||
 | 
					==============
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: compute.exceptions
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
@@ -45,3 +45,5 @@ Modules documentation
 | 
				
			|||||||
    session
 | 
					    session
 | 
				
			||||||
    instance/index
 | 
					    instance/index
 | 
				
			||||||
    storage/index
 | 
					    storage/index
 | 
				
			||||||
 | 
					    utils
 | 
				
			||||||
 | 
					    exceptions
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,3 +3,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.. automodule:: compute.instance.guest_agent
 | 
					.. automodule:: compute.instance.guest_agent
 | 
				
			||||||
   :members:
 | 
					   :members:
 | 
				
			||||||
 | 
					   :special-members: __init__
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,4 +3,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.. automodule:: compute.instance.instance
 | 
					.. automodule:: compute.instance.instance
 | 
				
			||||||
   :members:
 | 
					   :members:
 | 
				
			||||||
   :special-members:
 | 
					   :special-members: __init__
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,6 @@
 | 
				
			|||||||
``session``
 | 
					``session``
 | 
				
			||||||
===========
 | 
					===========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. autoclass:: compute.Session
 | 
					.. automodule:: compute.session
 | 
				
			||||||
   :members:
 | 
					 | 
				
			||||||
   :special-members:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.. autoclass:: compute.session.Capabilities
 | 
					 | 
				
			||||||
   :members:
 | 
					   :members:
 | 
				
			||||||
 | 
					   :special-members: __init__
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,3 +3,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.. automodule:: compute.storage.pool
 | 
					.. automodule:: compute.storage.pool
 | 
				
			||||||
   :members:
 | 
					   :members:
 | 
				
			||||||
 | 
					   :special-members: __init__
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,3 +3,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.. automodule:: compute.storage.volume
 | 
					.. automodule:: compute.storage.volume
 | 
				
			||||||
   :members:
 | 
					   :members:
 | 
				
			||||||
 | 
					   :special-members: __init__
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										14
									
								
								docs/source/python-api/utils.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								docs/source/python-api/utils.rst
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					``utils``
 | 
				
			||||||
 | 
					=========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``utils.units``
 | 
				
			||||||
 | 
					---------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: compute.utils.units
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``utils.ids``
 | 
				
			||||||
 | 
					-------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. automodule:: compute.utils.ids
 | 
				
			||||||
 | 
					   :members:
 | 
				
			||||||
							
								
								
									
										25
									
								
								fdict.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								fdict.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					from collections import UserDict
 | 
				
			||||||
 | 
					from typing import Any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _NotPresent:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Type for representing non-existent dictionary keys.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    See :class:`_FillableDict`
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _FillableDict(UserDict):
 | 
				
			||||||
 | 
					    """Use :method:`fill` to add key if not present."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, data: dict):
 | 
				
			||||||
 | 
					        self.data = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def fill(self, key: str, value: Any) -> None:
 | 
				
			||||||
 | 
					        if self.data.get(key, _NotPresent) is _NotPresent:
 | 
				
			||||||
 | 
					            self.data[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					d = _FillableDict({'a': None, 'b': 'BBBB'})
 | 
				
			||||||
 | 
					d.fill('c', 'CCCCCCCCC')
 | 
				
			||||||
 | 
					d.fill('a', 'CCCCCCCCC')
 | 
				
			||||||
 | 
					d['a'].fill('gg', 'AAAAAAAA')
 | 
				
			||||||
 | 
					print(d)
 | 
				
			||||||
							
								
								
									
										10
									
								
								instance.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								instance.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					title: dev-1
 | 
				
			||||||
 | 
					vcpus: 4
 | 
				
			||||||
 | 
					memory: 4096
 | 
				
			||||||
 | 
					volumes:
 | 
				
			||||||
 | 
					  - is_system: true
 | 
				
			||||||
 | 
					    type: file
 | 
				
			||||||
 | 
					    target: vda
 | 
				
			||||||
 | 
					    capacity:
 | 
				
			||||||
 | 
					      value: 5
 | 
				
			||||||
 | 
					      unit: GiB
 | 
				
			||||||
							
								
								
									
										34
									
								
								pars.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								pars.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _split_unit(val: str) -> dict | None:
 | 
				
			||||||
 | 
					    match = re.match(r'([0-9]+)([a-z]+)', val, re.I)
 | 
				
			||||||
 | 
					    if match:
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'value': match.groups()[0],
 | 
				
			||||||
 | 
					            'unit': match.groups()[1],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _parse_complex_arg(arg: str) -> dict:
 | 
				
			||||||
 | 
					    # key=value --> {'key': 'value'}
 | 
				
			||||||
 | 
					    if re.match(r'.+=.+', arg):
 | 
				
			||||||
 | 
					        key, val = arg.split('=')
 | 
				
			||||||
 | 
					    # system --> {'is_system': True}
 | 
				
			||||||
 | 
					    # ro --> {'is_readonly': True}
 | 
				
			||||||
 | 
					    elif re.match(r'^[a-z0-9_\.\-]+$', arg, re.I):
 | 
				
			||||||
 | 
					        key = 'is_' + arg.replace('ro', 'readonly')
 | 
				
			||||||
 | 
					        val = True
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        raise ValueError('Invalid argument pattern')
 | 
				
			||||||
 | 
					    # key=15GiB --> {'key': {'value': 15, 'unit': 'GiB'}}
 | 
				
			||||||
 | 
					    if not isinstance(val, bool):
 | 
				
			||||||
 | 
					        val = _split_unit(val) or val
 | 
				
			||||||
 | 
					    return {key: val}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(_parse_complex_arg('source=/volumes/50c4410b-2ef0-4ffd-a2e5-04f0212772d4.qcow2'))
 | 
				
			||||||
 | 
					print(_parse_complex_arg('capacity=15GiB'))
 | 
				
			||||||
 | 
					print(_parse_complex_arg('system'))
 | 
				
			||||||
 | 
					print(_parse_complex_arg('cpu.cores=8'))
 | 
				
			||||||
							
								
								
									
										20
									
								
								pd.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								pd.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					from pydantic import BaseModel, Extra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EntityModel(BaseModel):
 | 
				
			||||||
 | 
					    """Basic entity model."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    aaa: int
 | 
				
			||||||
 | 
					    bbb: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Config:
 | 
				
			||||||
 | 
					        extra = Extra.forbid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Some(EntityModel):
 | 
				
			||||||
 | 
					    ooo: str
 | 
				
			||||||
 | 
					    www: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a = Some(ooo='dsda', www='wcd', sds=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(a)
 | 
				
			||||||
							
								
								
									
										64
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										64
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							@@ -511,6 +511,66 @@ files = [
 | 
				
			|||||||
[package.extras]
 | 
					[package.extras]
 | 
				
			||||||
plugins = ["importlib-metadata"]
 | 
					plugins = ["importlib-metadata"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "pyyaml"
 | 
				
			||||||
 | 
					version = "6.0.1"
 | 
				
			||||||
 | 
					description = "YAML parser and emitter for Python"
 | 
				
			||||||
 | 
					category = "main"
 | 
				
			||||||
 | 
					optional = false
 | 
				
			||||||
 | 
					python-versions = ">=3.6"
 | 
				
			||||||
 | 
					files = [
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
 | 
				
			||||||
 | 
					    {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "requests"
 | 
					name = "requests"
 | 
				
			||||||
version = "2.31.0"
 | 
					version = "2.31.0"
 | 
				
			||||||
@@ -834,5 +894,5 @@ zstd = ["zstandard (>=0.18.0)"]
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[metadata]
 | 
					[metadata]
 | 
				
			||||||
lock-version = "2.0"
 | 
					lock-version = "2.0"
 | 
				
			||||||
python-versions = "^3.11"
 | 
					python-versions = '^3.11'
 | 
				
			||||||
content-hash = "413ca8b2e0d37bf9e2835dd9050a3cc98e4a37186c78b780a65d62d05adce8c1"
 | 
					content-hash = "e5c07eebe683b92360ec12cada14fc5ccbe4e4add52549bf978f580e551abfb0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,53 +1,60 @@
 | 
				
			|||||||
[tool.poetry]
 | 
					[tool.poetry]
 | 
				
			||||||
name = "compute"
 | 
					name = 'compute'
 | 
				
			||||||
version = "0.1.0"
 | 
					version = '0.1.0'
 | 
				
			||||||
description = "Library built on top of libvirt for Compute Service"
 | 
					description = 'Compute instances management library'
 | 
				
			||||||
authors = ["ge <ge@nixhacks.net>"]
 | 
					authors = ['ge <ge@nixhacks.net>']
 | 
				
			||||||
readme = "README.md"
 | 
					readme = 'README.md'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.dependencies]
 | 
					[tool.poetry.dependencies]
 | 
				
			||||||
python = "^3.11"
 | 
					python = '^3.11'
 | 
				
			||||||
libvirt-python = "9.0.0"
 | 
					libvirt-python = '9.0.0'
 | 
				
			||||||
lxml = "^4.9.2"
 | 
					lxml = '^4.9.2'
 | 
				
			||||||
pydantic = "1.10.4"
 | 
					pydantic = '1.10.4'
 | 
				
			||||||
 | 
					pyyaml = "^6.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.scripts]
 | 
					[tool.poetry.scripts]
 | 
				
			||||||
compute = "compute.cli.control:cli"
 | 
					compute = 'compute.cli.control:cli'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.group.dev.dependencies]
 | 
					[tool.poetry.group.dev.dependencies]
 | 
				
			||||||
ruff = "^0.1.3"
 | 
					ruff = '^0.1.3'
 | 
				
			||||||
isort = "^5.12.0"
 | 
					isort = '^5.12.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.poetry.group.docs.dependencies]
 | 
					[tool.poetry.group.docs.dependencies]
 | 
				
			||||||
sphinx = "^7.2.6"
 | 
					sphinx = '^7.2.6'
 | 
				
			||||||
sphinx-autobuild = "^2021.3.14"
 | 
					sphinx-autobuild = '^2021.3.14'
 | 
				
			||||||
sphinx-multiversion = "^0.2.4"
 | 
					sphinx-multiversion = '^0.2.4'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[build-system]
 | 
					[build-system]
 | 
				
			||||||
requires = ["poetry-core"]
 | 
					requires = ['poetry-core']
 | 
				
			||||||
build-backend = "poetry.core.masonry.api"
 | 
					build-backend = 'poetry.core.masonry.api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[tool.isort]
 | 
				
			||||||
 | 
					skip = ['.gitignore']
 | 
				
			||||||
 | 
					lines_after_imports = 2
 | 
				
			||||||
 | 
					include_trailing_comma = true
 | 
				
			||||||
 | 
					split_on_trailing_comma = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.ruff]
 | 
					[tool.ruff]
 | 
				
			||||||
line-length = 79
 | 
					line-length = 79
 | 
				
			||||||
indent-width = 4
 | 
					indent-width = 4
 | 
				
			||||||
target-version = "py311"
 | 
					target-version = 'py311'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.ruff.lint]
 | 
					[tool.ruff.lint]
 | 
				
			||||||
select = ["ALL"]
 | 
					select = ['ALL']
 | 
				
			||||||
ignore = [
 | 
					ignore = [
 | 
				
			||||||
    "Q000", "Q003", "D211", "D212", "ANN101", "ISC001", "COM812",
 | 
					    'Q000', 'Q003', 'D211', 'D212', 'ANN101', 'ISC001', 'COM812',
 | 
				
			||||||
    "D203", "ANN204", "T201",
 | 
					    'D203', 'ANN204', 'T201',
 | 
				
			||||||
    "EM102", "TRY003", # maybe not ignore?
 | 
					    'EM102', 'TRY003',  # maybe not ignore?
 | 
				
			||||||
    "TD003", "TD006", "FIX002",  # todo strings linting
 | 
					    'TD003', 'TD006', 'FIX002',  # todo strings linting
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
exclude = ["__init__.py"]
 | 
					exclude = ['__init__.py']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.ruff.lint.flake8-annotations]
 | 
					[tool.ruff.lint.flake8-annotations]
 | 
				
			||||||
mypy-init-return = true
 | 
					mypy-init-return = true
 | 
				
			||||||
allow-star-arg-any = true
 | 
					allow-star-arg-any = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.ruff.format]
 | 
					[tool.ruff.format]
 | 
				
			||||||
quote-style = "single"
 | 
					quote-style = 'single'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.ruff.isort]
 | 
					[tool.ruff.isort]
 | 
				
			||||||
lines-after-imports = 2
 | 
					lines-after-imports = 2
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user