diff --git a/src/openec2/actions/describe_instances.py b/src/openec2/actions/describe_instances.py index 9d18421..0684230 100644 --- a/src/openec2/actions/describe_instances.py +++ b/src/openec2/actions/describe_instances.py @@ -3,8 +3,8 @@ import uuid from fastapi import Response from fastapi.datastructures import QueryParams from sqlmodel import select -import libvirt +from openec2.libvirt import LibvirtSingleton from openec2.api.describe_instances import InstanceDescription, DescribeInstancesResponse, DescribeInstancesResponseReservationSet, describe_instance from openec2.api.shared import InstanceState from openec2.config import OpenEC2Config @@ -17,7 +17,7 @@ def describe_instances( db: DatabaseDep, ): response_items: list[InstanceDescription] = [] - conn = libvirt.open("qemu:///system") + conn = LibvirtSingleton.of().connection for instance in db.exec(select(Instance)).all(): dom = conn.lookupByName(instance.id) running = dom.isActive() @@ -25,7 +25,6 @@ def describe_instances( response_items.append( describe_instance(instance, dom), ) - conn.close() return Response( DescribeInstancesResponse( diff --git a/src/openec2/actions/run_instances.py b/src/openec2/actions/run_instances.py index a482fd5..6f2f531 100644 --- a/src/openec2/actions/run_instances.py +++ b/src/openec2/actions/run_instances.py @@ -6,8 +6,8 @@ import os from fastapi.datastructures import QueryParams from sqlmodel import select -import libvirt +from openec2.libvirt import LibvirtSingleton from openec2.config import OpenEC2Config from openec2.db import DatabaseDep from openec2.db.instance import Instance @@ -15,7 +15,6 @@ from openec2.db.image import AMI from openec2.api.run_instances import RunInstanceResponse, RunInstanceInstanceSet from openec2.api.describe_instances import describe_instance from openec2.utils.array import parse_array_objects -from openec2.utils.cloudinit import create_cloudinit_image def create_libvirt_domain( name: str, @@ -121,7 +120,7 @@ def run_instances( db.commit() print("Inserted new instance") - conn = libvirt.open("qemu:///system") + conn = LibvirtSingleton.of().connection domain = conn.defineXML( create_libvirt_domain( instance_id, @@ -133,7 +132,6 @@ def run_instances( ) domain.create() description = describe_instance(instance, domain) - conn.close() return RunInstanceResponse( request_id=uuid.uuid4().hex, diff --git a/src/openec2/actions/start_instances.py b/src/openec2/actions/start_instances.py index 0c927d3..ca6a39f 100644 --- a/src/openec2/actions/start_instances.py +++ b/src/openec2/actions/start_instances.py @@ -3,8 +3,8 @@ import uuid from fastapi import HTTPException from fastapi.datastructures import QueryParams from sqlmodel import select -import libvirt +from openec2.libvirt import LibvirtSingleton from openec2.config import OpenEC2Config from openec2.db import DatabaseDep from openec2.db.instance import Instance @@ -17,13 +17,13 @@ def start_instances( config: OpenEC2Config, db: DatabaseDep, ): + conn = LibvirtSingleton.of().connection instances: list[StartInstancesResponseInstancesSetInstance] = [] for instance_id in parse_array_plain("InstanceId", params): instance = db.exec(select(Instance).where(Instance.id == instance_id)).first() if instance is None: raise HTTPException(status_code=404, detail="Unknown instance") - conn = libvirt.open("qemu:///system") dom = conn.lookupByName(instance_id) running = dom.isActive() @@ -38,7 +38,6 @@ def start_instances( code=16 if running else 80, name="running" if running else "stopped", ) - conn.close() instances.append( StartInstancesResponseInstancesSetInstance( diff --git a/src/openec2/actions/stop_instances.py b/src/openec2/actions/stop_instances.py index 61bd5eb..61e4f38 100644 --- a/src/openec2/actions/stop_instances.py +++ b/src/openec2/actions/stop_instances.py @@ -3,8 +3,8 @@ import uuid from fastapi import HTTPException from fastapi.datastructures import QueryParams from sqlmodel import select -import libvirt +from openec2.libvirt import LibvirtSingleton from openec2.config import OpenEC2Config from openec2.db import DatabaseDep from openec2.db.instance import Instance @@ -18,13 +18,13 @@ def stop_instances( config: OpenEC2Config, db: DatabaseDep, ): + conn = LibvirtSingleton.of().connection instances: list[StopInstancesResponseInstancesSetInstance] = [] for instance_id in parse_array_plain("InstanceId", params): instance = db.exec(select(Instance).where(Instance.id == instance_id)).first() if instance is None: raise HTTPException(status_code=404, detail="Unknown instance") - conn = libvirt.open("qemu:///system") dom = conn.lookupByName(instance_id) running = dom.isActive() prev_state = InstanceState( @@ -38,7 +38,6 @@ def stop_instances( code=16 if running else 80, name="running" if running else "stopped", ) - conn.close() instances.append( StopInstancesResponseInstancesSetInstance( diff --git a/src/openec2/actions/terminate_instances.py b/src/openec2/actions/terminate_instances.py index 4dd5efe..4eb15c5 100644 --- a/src/openec2/actions/terminate_instances.py +++ b/src/openec2/actions/terminate_instances.py @@ -3,8 +3,8 @@ import logging from fastapi import HTTPException from fastapi.datastructures import QueryParams from sqlmodel import select -import libvirt +from openec2.libvirt import LibvirtSingleton from openec2.config import OpenEC2Config from openec2.db import DatabaseDep from openec2.db.instance import Instance @@ -18,17 +18,16 @@ def terminate_instances( config: OpenEC2Config, db: DatabaseDep, ): + conn = LibvirtSingleton.of().connection for instance_id in parse_array_plain("InstanceId", params): instance = db.exec(select(Instance).where(Instance.id == instance_id)).first() if instance is None: raise HTTPException(status_code=404, detail="Unknown instance") - conn = libvirt.open("qemu:///system") dom = conn.lookupByName(instance_id) if dom.isActive(): dom.shutdown() dom.undefine() - conn.close() # Delete files logger.debug(f"Removing {config.instances.location / instance_id}") diff --git a/src/openec2/api/run_instances.py b/src/openec2/api/run_instances.py index 6ac9cae..1eed661 100644 --- a/src/openec2/api/run_instances.py +++ b/src/openec2/api/run_instances.py @@ -1,5 +1,4 @@ from pydantic_xml import BaseXmlModel, element -import libvirt from openec2.db.instance import Instance from openec2.api.shared import InstanceState diff --git a/src/openec2/config.py b/src/openec2/config.py index 21a37ba..4415a14 100644 --- a/src/openec2/config.py +++ b/src/openec2/config.py @@ -13,12 +13,16 @@ class _OpenEC2InstanceConfig(BaseModel): types: dict[str, _OpenEC2InstanceType] +class _OpenEC2LibvirtConfig(BaseModel): + connection: str + class _OpenEC2Config(BaseModel): images: Path seed: Path instances: _OpenEC2InstanceConfig + libvirt: _OpenEC2LibvirtConfig -def get_config() -> _OpenEC2Config: +def _get_config() -> _OpenEC2Config: # TODO: Read from disk return _OpenEC2Config( images=Path("/home/alexander/openec2/images"), @@ -33,6 +37,26 @@ def get_config() -> _OpenEC2Config: ), }, ), + libvirt=_OpenEC2LibvirtConfig( + connection="qemu:///system" + ), ) -OpenEC2Config = Annotated[_OpenEC2Config, Depends(get_config)] +class ConfigSingleton: + __instance: "ConfigSingleton | None" = None + + config: _OpenEC2Config + + def __init__(self): + self.config = _get_config() + + def get_config(self) -> _OpenEC2Config: + return self.config + + @staticmethod + def of() -> "ConfigSingleton": + if ConfigSingleton.__instance is None: + ConfigSingleton.__instance = ConfigSingleton() + return ConfigSingleton.__instance + +OpenEC2Config = Annotated[_OpenEC2Config, Depends(ConfigSingleton.of().get_config)] diff --git a/src/openec2/libvirt.py b/src/openec2/libvirt.py new file mode 100644 index 0000000..649b529 --- /dev/null +++ b/src/openec2/libvirt.py @@ -0,0 +1,24 @@ +import libvirt + +from openec2.config import ConfigSingleton + + +class LibvirtSingleton: + __instance: "LibvirtSingleton | None" = None + + # The connection to libvirt + connection: libvirt.virConnect + + def __init__(self): + self.connection = libvirt.open( + ConfigSingleton.of().config.libvirt.connection, + ) + + def __del__(self): + self.connection.close() + + @staticmethod + def of() -> "LibvirtSingleton": + if LibvirtSingleton.__instance is None: + LibvirtSingleton.__instance = LibvirtSingleton() + return LibvirtSingleton.__instance diff --git a/src/openec2/utils/cloudinit.py b/src/openec2/utils/cloudinit.py deleted file mode 100644 index 8aa2672..0000000 --- a/src/openec2/utils/cloudinit.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -from pathlib import Path -import tempfile - -def create_cloudinit_image( - seed_file: Path, - instance_id: str, - user_data: str, -): - with tempfile.TemporaryDirectory() as _tmp: - tmp = Path(_tmp) - with (tmp / "meta-data").open("w") as f: - f.write(f"instance-id: {instance_id}\nlocal-hostname: {instance_id}") - with (tmp / "user-data").open("w") as f: - f.write(user_data) - - os.system(f"truncate --size 2M {seed_file}") - os.system(f"mkfs.vfat -n cidata {seed_file}") - os.system(f"mcopy -oi {seed_file} {tmp / "meta-data"} {tmp / "user-data"} ::")