This commit is contained in:
ge
2023-06-17 20:07:50 +03:00
commit b608d88265
12 changed files with 270 additions and 0 deletions

1
node_agent/__init__.py Normal file
View File

@ -0,0 +1 @@
from .main import NodeAgent

7
node_agent/base.py Normal file
View File

@ -0,0 +1,7 @@
import libvirt
class NodeAgentBase:
def __init__(self, conn: libvirt.virConnect, config: dict):
self.config = config
self.conn = conn

21
node_agent/config.py Normal file
View File

@ -0,0 +1,21 @@
import os
import sys
import pathlib
import tomllib
NODEAGENT_CONFIG_FILE = \
os.getenv('NODEAGENT_CONFIG_FILE') or '/etc/nodeagent/configuration.toml'
def load_config(config: pathlib.Path):
try:
with open(config, 'rb') as conf:
return tomllib.load(conf)
except (OSError, ValueError) as readerr:
sys.exit(f'Error: Cannot read configuration file: {readerr}')
except tomllib.TOMLDecodeError as tomlerr:
sys.exit(f'Error: Bad TOML syntax in configuration file: {tomlerr}')
config = load_config(pathlib.Path(NODEAGENT_CONFIG_FILE))

34
node_agent/exceptions.py Normal file
View File

@ -0,0 +1,34 @@
class VMNotFound(Exception):
def __init__(self, domain, message='VM not found: {domain}'):
self.domain = domain
self.message = message.format(domain=domain)
super().__init__(self.message)
class VMStartError(Exception):
def __init__(self, domain, message='VM start error: {domain}'):
self.domain = domain
self.message = message.format(domain=domain)
super().__init__(self.message)
class VMShutdownError(Exception):
def __init__(
self,
domain,
message="VM '{domain}' cannot shutdown, try with hard=True"
):
self.domain = domain
self.message = message.format(domain=domain)
super().__init__(self.message)
class VMRebootError(Exception):
def __init__(
self,
domain,
message="VM '{domain}' reboot, try with hard=True",
):
self.domain = domain
self.message = message.format(domain=domain)
super().__init__(self.message)

8
node_agent/main.py Normal file
View File

@ -0,0 +1,8 @@
import libvirt
from .vm import VirtualMachine
class NodeAgent:
def __init__(self, conn: libvirt.virConnect, config: dict):
self.vm = VirtualMachine(conn, config)

120
node_agent/vm.py Normal file
View File

@ -0,0 +1,120 @@
import libvirt
from .base import NodeAgentBase
from .exceptions import (
VMNotFound,
VMStartError,
VMRebootError,
VMShutdownError,
)
class VirtualMachine(NodeAgentBase):
def _dom(self, domain: str) -> libvirt.virDomain:
"""Get virDomain object to manipulate with domain."""
try:
ret = self.conn.lookupByName(domain)
if ret is not None:
return ret
raise VMNotFound(domain)
except libvirt.libvirtError as err:
raise VMNotFound(err) from err
def create(
self,
name: str,
volumes: list[dict],
vcpus: int,
vram: int,
image: dict,
cdrom: dict | None = None,
):
# TODO
pass
def delete(self, name: str, delete_volumes=False):
pass
def status(self, name: str) -> str:
"""
Return VM state: 'running', 'shutoff', etc. Ref:
https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainState
"""
state = self._dom(name).info()[0]
match state:
case libvirt.VIR_DOMAIN_NOSTATE:
return 'nostate'
case libvirt.VIR_DOMAIN_RUNNING:
return 'running'
case libvirt.VIR_DOMAIN_BLOCKED:
return 'blocked'
case libvirt.VIR_DOMAIN_PAUSED:
return 'paused'
case libvirt.VIR_DOMAIN_SHUTDOWN:
return 'shutdown'
case libvirt.VIR_DOMAIN_SHUTOFF:
return 'shutoff'
case libvirt.VIR_DOMAIN_CRASHED:
return 'crashed'
case libvirt.VIR_DOMAIN_PMSUSPENDED:
return 'pmsuspended'
def is_running(self, name: str) -> bool:
"""Return True if VM is running, else return False."""
if self._dom(name).isActive() != 1:
return False # inactive (0) or error (-1)
return True
def start(self, name: str) -> None:
"""Start VM."""
if not self.is_running(name):
ret = self._dom(name).create()
else:
return
if ret != 0:
raise VMStartError(name)
def shutdown(self, name: str, hard=False) -> None:
"""Shutdown VM. Use hard=True to force shutdown."""
if hard:
# Destroy VM gracefully (no SIGKILL)
ret = self._dom(name).destroyFlags(flags=libvirt.VIR_DOMAIN_DESTROY_GRACEFUL)
else:
# Normal VM shutdown, OS may ignore this.
ret = self._dom(name).shutdown()
if ret != 0:
raise VMShutdownError(name)
def reboot(self, name: str, hard=False) -> None:
"""
Reboot VM. Use hard=True to force reboot. With forced reboot
VM will shutdown via self.shutdown() (no forced) and started.
"""
if hard:
# Forced "reboot"
self.shutdown(name)
self.start(name)
else:
# Normal reboot.
ret = self._dom(name).reboot()
if ret != 0:
raise VMRebootError(name)
def vcpu_set(self, name: str, count: int):
pass
def vram_set(self, name: str, count: int):
pass
def ssh_keys_list(self, name: str, user: str):
pass
def ssh_keys_add(self, name: str, user: str):
pass
def ssh_keys_remove(self, name: str, user: str):
pass
def set_user_password(self, name: str, user: str):
pass