forked from clan/clan-core
clan-vm-manager: Fixing vm starting.
This commit is contained in:
parent
4f7f34f9b4
commit
280bee0861
@ -32,7 +32,9 @@ class VMAttr:
|
||||
log.debug(f"qmp_socket: {self._qmp_socket}")
|
||||
rpath = self._qmp_socket.resolve()
|
||||
if not rpath.exists():
|
||||
raise ClanError(f"qmp socket {rpath} does not exist. Is the VM running?")
|
||||
raise ClanError(
|
||||
f"qmp socket {rpath} does not exist. Is the VM running?"
|
||||
)
|
||||
self._qmp = QEMUMonitorProtocol(str(rpath))
|
||||
self._qmp.connect()
|
||||
try:
|
||||
|
@ -37,7 +37,7 @@ def facts_to_nixos_config(facts: dict[str, dict[str, bytes]]) -> dict:
|
||||
|
||||
# TODO move this to the Machines class
|
||||
def build_vm(
|
||||
machine: Machine, vm: VmConfig, tmpdir: Path, nix_options: list[str]
|
||||
machine: Machine, vm: VmConfig, tmpdir: Path, nix_options: list[str] = []
|
||||
) -> dict[str, str]:
|
||||
secrets_dir = get_secrets(machine, tmpdir)
|
||||
|
||||
|
@ -43,7 +43,9 @@ def wait_vm_up(state_dir: Path) -> None:
|
||||
timeout: float = 300
|
||||
while True:
|
||||
if timeout <= 0:
|
||||
raise TimeoutError(f"qga socket {socket_file} not found. Is the VM running?")
|
||||
raise TimeoutError(
|
||||
f"qga socket {socket_file} not found. Is the VM running?"
|
||||
)
|
||||
if socket_file.exists():
|
||||
break
|
||||
sleep(0.1)
|
||||
@ -56,7 +58,9 @@ def wait_vm_down(state_dir: Path) -> None:
|
||||
timeout: float = 300
|
||||
while socket_file.exists():
|
||||
if timeout <= 0:
|
||||
raise TimeoutError(f"qga socket {socket_file} still exists. Is the VM down?")
|
||||
raise TimeoutError(
|
||||
f"qga socket {socket_file} still exists. Is the VM down?"
|
||||
)
|
||||
sleep(0.1)
|
||||
timeout -= 0.1
|
||||
|
||||
|
@ -14,7 +14,7 @@ from gi.repository import Adw, Gdk, Gio, Gtk
|
||||
|
||||
from clan_vm_manager.models.interfaces import ClanConfig
|
||||
from clan_vm_manager.models.use_join import GLib, GObject
|
||||
from clan_vm_manager.models.use_vms import VMS
|
||||
from clan_vm_manager.models.use_vms import VMs
|
||||
|
||||
from .trayicon import TrayIcon
|
||||
from .windows.main_window import MainWindow
|
||||
@ -44,7 +44,8 @@ class MainApplication(Adw.Application):
|
||||
"enable debug mode",
|
||||
None,
|
||||
)
|
||||
|
||||
self.vms = VMs.use()
|
||||
log.debug(f"VMS object: {self.vms}")
|
||||
self.window: Adw.ApplicationWindow | None = None
|
||||
self.connect("shutdown", self.on_shutdown)
|
||||
self.connect("activate", self.show_window)
|
||||
@ -69,35 +70,16 @@ class MainApplication(Adw.Application):
|
||||
log.debug(f"Join request: {args[1]}")
|
||||
uri = args[1]
|
||||
self.emit("join_request", uri)
|
||||
|
||||
return 0
|
||||
|
||||
def get_application_icon_path(self) -> None:
|
||||
self.icon_name = "lol.clan.vm.manager"
|
||||
if not self.icon_name:
|
||||
return None
|
||||
|
||||
icon_theme = Gtk.IconTheme.get_for_display(
|
||||
self.get_active_window().get_display()
|
||||
)
|
||||
# Use the correct method to look up an icon
|
||||
icon_lookup_flags = 16
|
||||
icon = icon_theme.lookup_icon(
|
||||
self.icon_name, 128, 1.0, Gtk.TextDirection.NONE, icon_lookup_flags
|
||||
)
|
||||
|
||||
if icon:
|
||||
return icon.get_file().get_path()
|
||||
return None
|
||||
|
||||
def on_shutdown(self, app: Gtk.Application) -> None:
|
||||
log.debug("Shutting down")
|
||||
|
||||
self.vms.kill_all()
|
||||
|
||||
if self.tray_icon is not None:
|
||||
self.tray_icon.destroy()
|
||||
|
||||
VMS.use().kill_all()
|
||||
|
||||
def on_window_hide_unhide(self, *_args: Any) -> None:
|
||||
assert self.window is not None
|
||||
if self.window.is_visible():
|
||||
|
@ -7,7 +7,6 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import gi
|
||||
from clan_cli.errors import ClanError
|
||||
|
||||
gi.require_version("GdkPixbuf", "2.0")
|
||||
|
||||
@ -24,7 +23,7 @@ def _kill_group(proc: mp.Process) -> None:
|
||||
if proc.is_alive() and pid:
|
||||
os.killpg(pid, signal.SIGTERM)
|
||||
else:
|
||||
log.warning(f"Process {proc.name} with pid {pid} is already dead")
|
||||
log.warning(f"Process '{proc.name}' with pid '{pid}' is already dead")
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@ -102,7 +101,7 @@ def _init_proc(
|
||||
|
||||
def spawn(
|
||||
*,
|
||||
log_dir: Path,
|
||||
out_file: Path,
|
||||
on_except: Callable[[Exception, mp.process.BaseProcess], None] | None,
|
||||
func: Callable,
|
||||
**kwargs: Any,
|
||||
@ -111,13 +110,8 @@ def spawn(
|
||||
if mp.get_start_method(allow_none=True) is None:
|
||||
mp.set_start_method(method="forkserver")
|
||||
|
||||
if not log_dir.is_dir():
|
||||
raise ClanError(f"Log path {log_dir} is not a directory")
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Set names
|
||||
proc_name = f"MPExec:{func.__name__}"
|
||||
out_file = log_dir / "out.log"
|
||||
|
||||
# Start the process
|
||||
proc = mp.Process(
|
||||
|
@ -9,7 +9,7 @@ from clan_cli.clan_uri import ClanURI
|
||||
from clan_cli.history.add import add_history
|
||||
|
||||
from clan_vm_manager.errors.show_error import show_error_dialog
|
||||
from clan_vm_manager.models.use_vms import VMS, Clans
|
||||
from clan_vm_manager.models.use_vms import Clans
|
||||
|
||||
gi.require_version("Gtk", "4.0")
|
||||
gi.require_version("Adw", "1")
|
||||
@ -76,7 +76,7 @@ class Join:
|
||||
def after_join(item: JoinValue, _: Any) -> None:
|
||||
self.discard(item)
|
||||
Clans.use().refresh()
|
||||
VMS.use().refresh()
|
||||
# VMS.use().refresh()
|
||||
print("Refreshed list after join")
|
||||
on_join(item)
|
||||
|
||||
|
@ -107,6 +107,7 @@ class VM(GObject.Object):
|
||||
data: HistoryEntry,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.KILL_TIMEOUT = 6 # seconds
|
||||
self.data = data
|
||||
self.process = MPProcess("dummy", mp.Process(), Path("./dummy"))
|
||||
self._watcher_id: int = 0
|
||||
@ -121,9 +122,8 @@ class VM(GObject.Object):
|
||||
self.log_dir = tempfile.TemporaryDirectory(
|
||||
prefix="clan_vm-", suffix=f"-{self.data.flake.flake_attr}"
|
||||
)
|
||||
self._finalizer = weakref.finalize(self, self.stop)
|
||||
self._finalizer = weakref.finalize(self, self.kill)
|
||||
self.connect("build_vm", self.build_vm)
|
||||
|
||||
uri = ClanURI.from_str(
|
||||
url=self.data.flake.flake_url, flake_attr=self.data.flake.flake_attr
|
||||
)
|
||||
@ -160,25 +160,25 @@ class VM(GObject.Object):
|
||||
log.info(f"Starting VM {self.get_id()}")
|
||||
vm = vms.run.inspect_vm(self.machine)
|
||||
|
||||
GLib.idle_add(self.emit, "build_vm", self, True)
|
||||
self.process = spawn(
|
||||
on_except=None,
|
||||
log_dir=Path(str(self.log_dir.name)),
|
||||
func=vms.run.build_vm,
|
||||
machine=self.machine,
|
||||
vm=vm,
|
||||
)
|
||||
self.process.proc.join()
|
||||
# GLib.idle_add(self.emit, "build_vm", self, True)
|
||||
# self.process = spawn(
|
||||
# on_except=None,
|
||||
# log_dir=Path(str(self.log_dir.name)),
|
||||
# func=vms.run.build_vm,
|
||||
# machine=self.machine,
|
||||
# vm=vm,
|
||||
# )
|
||||
# self.process.proc.join()
|
||||
|
||||
GLib.idle_add(self.emit, "build_vm", self, False)
|
||||
# GLib.idle_add(self.emit, "build_vm", self, False)
|
||||
|
||||
if self.process.proc.exitcode != 0:
|
||||
log.error(f"Failed to build VM {self.get_id()}")
|
||||
return
|
||||
# if self.process.proc.exitcode != 0:
|
||||
# log.error(f"Failed to build VM {self.get_id()}")
|
||||
# return
|
||||
|
||||
self.process = spawn(
|
||||
on_except=None,
|
||||
log_dir=Path(str(self.log_dir.name)),
|
||||
out_file=Path(str(self.log_dir.name)) / "vm.log",
|
||||
func=vms.run.run_vm,
|
||||
vm=vm,
|
||||
)
|
||||
@ -241,7 +241,7 @@ class VM(GObject.Object):
|
||||
if self.is_running():
|
||||
assert self._stop_timer_init is not None
|
||||
diff = datetime.now() - self._stop_timer_init
|
||||
if diff.seconds > 10:
|
||||
if diff.seconds > self.KILL_TIMEOUT:
|
||||
log.error(f"VM {self.get_id()} has not stopped. Killing it")
|
||||
self.process.kill_group()
|
||||
return GLib.SOURCE_CONTINUE
|
||||
@ -263,12 +263,19 @@ class VM(GObject.Object):
|
||||
if self._stop_watcher_id == 0:
|
||||
raise ClanError("Failed to add stop watcher")
|
||||
|
||||
def stop(self) -> None:
|
||||
def shutdown(self) -> None:
|
||||
if not self.is_running():
|
||||
return
|
||||
log.info(f"Stopping VM {self.get_id()}")
|
||||
threading.Thread(target=self.__stop).start()
|
||||
|
||||
def kill(self) -> None:
|
||||
if not self.is_running():
|
||||
log.warning(f"Tried to kill VM {self.get_id()} is not running")
|
||||
return
|
||||
log.info(f"Killing VM {self.get_id()} now")
|
||||
self.process.kill_group()
|
||||
|
||||
def read_whole_log(self) -> str:
|
||||
if not self.process.out_file.exists():
|
||||
log.error(f"Log file {self.process.out_file} does not exist")
|
||||
@ -276,28 +283,16 @@ class VM(GObject.Object):
|
||||
return self.process.out_file.read_text()
|
||||
|
||||
|
||||
class VMS:
|
||||
"""
|
||||
This is a singleton.
|
||||
It is initialized with the first call of use()
|
||||
|
||||
Usage:
|
||||
|
||||
VMS.use().get_running_vms()
|
||||
|
||||
VMS.use() can also be called before the data is needed. e.g. to eliminate/reduce waiting time.
|
||||
|
||||
"""
|
||||
|
||||
class VMs:
|
||||
list_store: Gio.ListStore
|
||||
_instance: "None | VMS" = None
|
||||
_instance: "None | VMs" = None
|
||||
|
||||
# Make sure the VMS class is used as a singleton
|
||||
def __init__(self) -> None:
|
||||
raise RuntimeError("Call use() instead")
|
||||
|
||||
@classmethod
|
||||
def use(cls: Any) -> "VMS":
|
||||
def use(cls: Any) -> "VMs":
|
||||
if cls._instance is None:
|
||||
cls._instance = cls.__new__(cls)
|
||||
cls.list_store = Gio.ListStore.new(VM)
|
||||
@ -327,10 +322,13 @@ class VMS:
|
||||
return list(filter(lambda vm: vm.is_running(), self.list_store))
|
||||
|
||||
def kill_all(self) -> None:
|
||||
log.debug(f"Running vms: {self.get_running_vms()}")
|
||||
for vm in self.get_running_vms():
|
||||
vm.stop()
|
||||
vm.kill()
|
||||
|
||||
def refresh(self) -> None:
|
||||
log.error("NEVER FUCKING DO THIS")
|
||||
return
|
||||
self.list_store.remove_all()
|
||||
for vm in get_saved_vms():
|
||||
self.list_store.append(vm)
|
||||
@ -338,7 +336,7 @@ class VMS:
|
||||
|
||||
def get_saved_vms() -> list[VM]:
|
||||
vm_list = []
|
||||
|
||||
log.info("=====CREATING NEW VM OBJ====")
|
||||
try:
|
||||
# Execute `clan flakes add <path>` to democlan for this to work
|
||||
for entry in list_history():
|
||||
|
@ -14,7 +14,7 @@ from clan_vm_manager.models.use_views import Views
|
||||
gi.require_version("Adw", "1")
|
||||
from gi.repository import Adw, Gdk, Gio, GLib, GObject, Gtk
|
||||
|
||||
from clan_vm_manager.models.use_vms import VM, VMS, ClanGroup, Clans
|
||||
from clan_vm_manager.models.use_vms import VM, ClanGroup, Clans
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -48,8 +48,8 @@ class ClanList(Gtk.Box):
|
||||
def __init__(self, config: ClanConfig) -> None:
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
app = Gio.Application.get_default()
|
||||
app.connect("join_request", self.on_join_request)
|
||||
self.app = Gio.Application.get_default()
|
||||
self.app.connect("join_request", self.on_join_request)
|
||||
|
||||
groups = Clans.use()
|
||||
join = Join.use()
|
||||
@ -123,7 +123,7 @@ class ClanList(Gtk.Box):
|
||||
def on_search_changed(self, entry: Gtk.SearchEntry) -> None:
|
||||
Clans.use().filter_by_name(entry.get_text())
|
||||
# Disable the shadow if the list is empty
|
||||
if not VMS.use().list_store.get_n_items():
|
||||
if not self.app.vms.list_store.get_n_items():
|
||||
self.group_list.add_css_class("no-shadow")
|
||||
|
||||
def render_vm_row(self, boxed_list: Gtk.ListBox, vm: VM) -> Gtk.Widget:
|
||||
@ -202,7 +202,7 @@ class ClanList(Gtk.Box):
|
||||
|
||||
def on_edit(self, action: Any, parameter: Any) -> None:
|
||||
target = parameter.get_string()
|
||||
vm = VMS.use().get_by_id(target)
|
||||
vm = self.app.vms.get_by_id(target)
|
||||
|
||||
if not vm:
|
||||
raise ClanError("Something went wrong. Please restart the app.")
|
||||
@ -220,7 +220,7 @@ class ClanList(Gtk.Box):
|
||||
row.add_css_class("trust")
|
||||
|
||||
# TODO: figure out how to detect that
|
||||
exist = VMS.use().get_by_id(item.url.get_id())
|
||||
exist = self.app.vms.use().get_by_id(item.url.get_id())
|
||||
if exist:
|
||||
sub = row.get_subtitle()
|
||||
row.set_subtitle(
|
||||
@ -292,7 +292,7 @@ class ClanList(Gtk.Box):
|
||||
|
||||
if not row.get_active():
|
||||
row.set_state(True)
|
||||
vm.stop()
|
||||
vm.shutdown()
|
||||
|
||||
def vm_status_changed(self, switch: Gtk.Switch, vm: VM, _vm: VM) -> None:
|
||||
switch.set_active(vm.is_running())
|
||||
|
Loading…
Reference in New Issue
Block a user