diff --git a/nixos/tests/admin.nix b/nixos/tests/admin.nix index 439900f..06ebb50 100644 --- a/nixos/tests/admin.nix +++ b/nixos/tests/admin.nix @@ -49,6 +49,10 @@ in addr = addrs.adminvm; port = "9001"; tls = mkTls "admin-vm"; + services = [ + "display-suspend.service" + "display-resume.service" + ]; }; }; hostvm = { @@ -94,6 +98,12 @@ in snakeOilPrivateKey snakeOilPublicKey ; + NotifyDisplaySuspend = pkgs.writeShellScript "NotifyDisplaySuspend" '' + echo 'Service notification: Dummy display suspend service started successfully.' + ''; + NotifyDisplayResume = pkgs.writeShellScript "NotifyDisplayResume" '' + echo 'Service notification: Dummy display resume service started successfully.' + ''; in { imports = [ self.nixosModules.sysvm ]; @@ -107,6 +117,27 @@ in openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; }; }; + + systemd.services.display-suspend = { + enable = true; + description = "Dummy display suspend service"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${NotifyDisplaySuspend}"; + RemainAfterExit = true; + }; + }; + + systemd.services.display-resume = { + enable = true; + description = "Dummy display resume service"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${NotifyDisplayResume}"; + RemainAfterExit = true; + }; + }; + services.getty.autologinUser = "ghaf"; # End of users @@ -158,6 +189,8 @@ in "reboot.target" "sleep.target" "suspend.target" + "display-suspend.service" + "display-resume.service" ]; }; @@ -322,6 +355,17 @@ in print(hostvm.succeed("${cli} --addr ${nodes.adminvm.config.givc.admin.addr} --port ${nodes.adminvm.config.givc.admin.port} --cacert ${nodes.hostvm.givc.host.tls.caCertPath} --cert ${nodes.hostvm.givc.host.tls.certPath} --key ${nodes.hostvm.givc.host.tls.keyPath} ${if tls then "" else "--notls"} --name ${nodes.adminvm.config.givc.admin.name} start --vm foot-vm clearexit")) time.sleep(20) # Give few seconds to application to spin up, exit, then start it again print(hostvm.succeed("${cli} --addr ${nodes.adminvm.config.givc.admin.addr} --port ${nodes.adminvm.config.givc.admin.port} --cacert ${nodes.hostvm.givc.host.tls.caCertPath} --cert ${nodes.hostvm.givc.host.tls.certPath} --key ${nodes.hostvm.givc.host.tls.keyPath} ${if tls then "" else "--notls"} --name ${nodes.adminvm.config.givc.admin.name} start --vm foot-vm clearexit")) + + with subtest("suspend system"): + print(hostvm.succeed("${cli} --addr ${nodes.adminvm.config.givc.admin.addr} --port ${nodes.adminvm.config.givc.admin.port} --cacert ${nodes.hostvm.givc.host.tls.caCertPath} --cert ${nodes.hostvm.givc.host.tls.certPath} --key ${nodes.hostvm.givc.host.tls.keyPath} ${if tls then "" else "--notls"} --name ${nodes.adminvm.config.givc.admin.name} suspend")) + time.sleep(10) # Give few seconds to application to spin up + guivm.wait_for_unit("display-suspend.service") + + with subtest("wakeup system"): + print(hostvm.succeed("${cli} --addr ${nodes.adminvm.config.givc.admin.addr} --port ${nodes.adminvm.config.givc.admin.port} --cacert ${nodes.hostvm.givc.host.tls.caCertPath} --cert ${nodes.hostvm.givc.host.tls.certPath} --key ${nodes.hostvm.givc.host.tls.keyPath} ${if tls then "" else "--notls"} --name ${nodes.adminvm.config.givc.admin.name} wakeup")) + time.sleep(10) # Give few seconds to application to spin up + guivm.wait_for_unit("display-resume.service") + ''; }; }; diff --git a/src/admin/server.rs b/src/admin/server.rs index 5b23354..e0bd989 100644 --- a/src/admin/server.rs +++ b/src/admin/server.rs @@ -158,6 +158,37 @@ impl AdminServiceImpl { Ok(()) } + pub async fn start_unit_on_vm(&self, vmname: &str, unit: &str) -> anyhow::Result<()> { + let vmservice = format_service_name(vmname, None); + + /* Return error if the vm is not registered */ + let endpoint = self.agent_endpoint(&vmservice)?; + let client = SystemDClient::new(endpoint); + + /* Check status of the unit */ + match client.get_remote_status(unit.into()).await { + Ok(status) => { + if status.load_state == "loaded" { + /* No action, if the unit is loaded and already running. */ + if status.active_state == "active" && status.sub_state == "running" { + info!("Service {unit} is already in running state!"); + } else { + /* Start the unit if it is loaded and not running. */ + client.start_remote(unit.into()).await?; + } + return Ok(()); + } else { + /* Error, if the unit is not loaded. */ + bail!("Service {unit} is not loaded!"); + } + } + Err(e) => { + eprintln!("Error retrieving unit status: {}", e); + return Err(e.into()); + } + } + } + pub async fn start_vm(&self, name: &str) -> anyhow::Result<()> { let endpoint = self.host_endpoint()?; let client = SystemDClient::new(endpoint); @@ -454,19 +485,20 @@ impl pb::admin_service_server::AdminService for AdminService { ) -> std::result::Result, tonic::Status> { escalate(request, |_| async { self.inner - .send_system_command(String::from("suspend.target")) + .start_unit_on_vm("gui", "display-suspend.service") .await?; Ok(Empty {}) }) .await } + async fn wakeup( &self, request: tonic::Request, ) -> std::result::Result, tonic::Status> { escalate(request, |_| async { self.inner - .send_system_command(String::from("sleep.target")) + .start_unit_on_vm("gui", "display-resume.service") .await?; Ok(Empty {}) })