Skip to content

Commit

Permalink
Feat: Add capture for windows signals
Browse files Browse the repository at this point in the history
Adds code to capture windows signals in the runtime. Includes tests
for starting and killing the runtime, also includes a change so github actions
will build a windows container.
  • Loading branch information
euamcg committed Aug 8, 2023
1 parent 09b0f67 commit 6bb32c9
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ concurrency:

jobs:
release-please:
runs-on: ubuntu-latest
runs-on: [ubuntu-latest, windows-latest]
if: >
github.ref == 'refs/heads/main' &&
github.repository_owner == 'ipvm-wg' &&
Expand Down
9 changes: 9 additions & 0 deletions homestar-runtime/fixtures/test_windows_v4.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[monitoring]
process_collector_interval = 10

[node]

[node.network]
events_buffer_len = 1000
rpc_port = 9999
rpc_host = "127.0.0.1"
3 changes: 2 additions & 1 deletion homestar-runtime/src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use anyhow::Result;
use std::path::PathBuf;

#[cfg(not(windows))]
const PID_FILE: &str = "homestar.pid";

/// Start the Homestar runtime as a daemon.
Expand All @@ -18,6 +19,6 @@ pub fn start(dir: PathBuf) -> Result<()> {

/// Start the Homestar runtime as a daemon.
#[cfg(windows)]
pub fn start(dir: PathBuf) -> Result<()> {
pub fn start(_dir: PathBuf) -> Result<()> {
Err(anyhow::anyhow!("Daemonizing is not supported on Windows"))
}
34 changes: 27 additions & 7 deletions homestar-runtime/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ use libipld::Cid;
#[cfg(not(test))]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{ops::ControlFlow, rc::Rc, sync::Arc, task::Poll};
#[cfg(not(windows))]
use tokio::signal::unix::{signal, SignalKind};
#[cfg(windows)]
use tokio::signal::windows;
use tokio::{
runtime, select,
signal::unix::{signal, SignalKind},
sync::{mpsc, oneshot},
task::{AbortHandle, JoinHandle},
time,
Expand Down Expand Up @@ -417,13 +420,30 @@ impl Runner {
/// Captures shutdown signals for [Runner].
#[allow(dead_code)]
async fn shutdown_signal() -> Result<()> {
let mut sigint = signal(SignalKind::interrupt())?;
let mut sigterm = signal(SignalKind::terminate())?;
#[cfg(not(windows))]
{
let mut sigint = signal(SignalKind::interrupt())?;
let mut sigterm = signal(SignalKind::terminate())?;

select! {
_ = tokio::signal::ctrl_c() => info!("CTRL-C received, shutting down"),
_ = sigint.recv() => info!("SIGINT received, shutting down"),
_ = sigterm.recv() => info!("SIGTERM received, shutting down"),
}
}

select! {
_ = tokio::signal::ctrl_c() => info!("CTRL-C received, shutting down"),
_ = sigint.recv() => info!("SIGINT received, shutting down"),
_ = sigterm.recv() => info!("SIGTERM received, shutting down"),
#[cfg(windows)]
{
let mut sigint = windows::ctrl_close()?;
let mut sigterm = windows::ctrl_shutdown()?;
let mut sighup = windows::ctrl_break()?;

select! {
_ = tokio::signal::ctrl_c() => info!("CTRL-C received, shutting down"),
_ = sigint.recv() => info!("SIGINT received, shutting down"),
_ = sigterm.recv() => info!("SIGTERM received, shutting down"),
_ = sighup.recv() => info!("SIGHUP received, shutting down")
}
}

Ok(())
Expand Down
200 changes: 198 additions & 2 deletions homestar-runtime/tests/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::{Context, Result};
use assert_cmd::{crate_name, prelude::*};
#[cfg(not(windows))]
use nix::{
sys::signal::{self, Signal},
unistd::Pid,
Expand All @@ -9,13 +10,12 @@ use predicates::prelude::*;
use retry::{delay::Fixed, retry};
use serial_test::serial;
use std::{
fs,
net::{IpAddr, Ipv6Addr, Shutdown, SocketAddr, TcpStream},
path::PathBuf,
process::{Command, Stdio},
time::Duration,
};
use sysinfo::{PidExt, ProcessExt, SystemExt};
use sysinfo::{ProcessExt, SystemExt};
use wait_timeout::ChildExt;

static BIN: Lazy<PathBuf> = Lazy::new(|| assert_cmd::cargo::cargo_bin(crate_name!()));
Expand Down Expand Up @@ -80,6 +80,7 @@ fn test_version_serial() -> Result<()> {

#[test]
#[serial]
#[cfg(not(windows))]
fn test_server_not_running_serial() -> Result<()> {
let _ = stop_bin();

Expand Down Expand Up @@ -123,6 +124,7 @@ fn test_server_not_running_serial() -> Result<()> {

#[test]
#[serial]
#[cfg(not(windows))]
fn test_server_serial() -> Result<()> {
let _ = stop_bin();

Expand Down Expand Up @@ -185,6 +187,7 @@ fn test_server_serial() -> Result<()> {
#[cfg(feature = "test-utils")]
#[test]
#[serial]
#[cfg(not(windows))]
fn test_workflow_run_serial() -> Result<()> {
let _ = stop_bin();

Expand Down Expand Up @@ -257,6 +260,9 @@ fn test_workflow_run_serial() -> Result<()> {
#[serial]
#[cfg(not(windows))]
fn test_daemon_serial() -> Result<()> {
use std::fs;
use sysinfo::PidExt;

let _ = stop_bin();

Command::new(BIN.as_os_str())
Expand Down Expand Up @@ -309,3 +315,193 @@ fn test_daemon_serial() -> Result<()> {

Ok(())
}

#[test]
#[serial]
#[cfg(windows)]
fn test_server_not_running_serial_windows() -> Result<()> {
let _ = stop_bin();

Command::new(BIN.as_os_str())
.arg("ping")
.assert()
.failure()
.stderr(predicate::str::contains("No connection could be made"));

Command::new(BIN.as_os_str())
.arg("ping")
.arg("--host")
.arg("::1")
.assert()
.failure()
.stderr(predicate::str::contains("No connection could be made"));

Command::new(BIN.as_os_str())
.arg("ping")
.arg("--host")
.arg("::2")
.assert()
.failure()
.stderr(predicate::str::contains("unreachable network"));

Command::new(BIN.as_os_str())
.arg("stop")
.assert()
.failure()
.stderr(predicate::str::contains("No connection could be made"));
let _ = stop_bin();

Ok(())
}

#[test]
#[serial]
#[cfg(windows)]
fn test_server_serial_windows() -> Result<()> {
let _ = stop_bin();

Command::new(BIN.as_os_str())
.arg("start")
.arg("-db")
.arg("homestar.db")
.assert()
.failure();

let mut homestar_proc = Command::new(BIN.as_os_str())
.arg("start")
.arg("--db")
.arg("homestar.db")
.stdout(Stdio::piped())
.spawn()
.unwrap();

let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 3030);
let result = retry(Fixed::from_millis(500), || {
TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both))
});

if result.is_err() {
homestar_proc.kill().unwrap();
panic!("Homestar server/runtime failed to start in time");
}

Command::new(BIN.as_os_str())
.arg("ping")
.assert()
.success()
.stdout(predicate::str::contains("::1"))
.stdout(predicate::str::contains("pong"));

Command::new(BIN.as_os_str())
.arg("ping")
.arg("-p")
.arg("9999")
.assert()
.failure()
.stderr(predicate::str::contains("No connection could be made"));

let _ = Command::new(BIN.as_os_str()).arg("stop").output();

if let Ok(None) = homestar_proc.try_wait() {
let _status_code = match homestar_proc.wait_timeout(Duration::from_secs(1)).unwrap() {
Some(status) => status.code(),
None => {
homestar_proc.kill().unwrap();
homestar_proc.wait().unwrap().code()
}
};
}
let _ = stop_bin();

Ok(())
}

#[test]
#[serial]
#[cfg(windows)]
fn test_windows_signal_kill() -> Result<()> {
let _ = stop_bin();

Command::new(BIN.as_os_str())
.arg("start")
.arg("--db")
.arg("homestar.db")
.stdout(Stdio::piped())
.spawn()
.unwrap();

let system = sysinfo::System::new_all();
let pid = system
.processes_by_exact_name("homestar-runtime.exe")
.collect::<Vec<_>>()
.first()
.map(| x | x.pid())
.unwrap();

let socket = SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 3030);
let result = retry(Fixed::from_millis(500), || {
TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both))
});

if result.is_err() {
panic!("Homestar server/runtime failed to start in time");
}

Command::new(BIN.as_os_str())
.arg("ping")
.assert()
.success()
.stdout(predicate::str::contains("::1"))
.stdout(predicate::str::contains("pong"));

if let Some(process) = system.process(pid) {
process.kill();
};

Command::new(BIN.as_os_str()).arg("ping").assert().failure();
let _ = stop_bin();

Ok(())
}

#[test]
#[serial]
#[cfg(windows)]
fn test_server_serial_windows_v4() -> Result<()> {
use std::net::Ipv4Addr;

let _ = stop_bin();

let mut homestar_proc = Command::new(BIN.as_os_str())
.arg("start")
.arg("-c")
.arg("fixtures/test_windows_v4.toml")
.arg("--db")
.arg("homestar.db")
.stdout(Stdio::piped())
.spawn()
.unwrap();

let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9999);
let result = retry(Fixed::from_millis(500), || {
TcpStream::connect(socket).map(|stream| stream.shutdown(Shutdown::Both))
});

if result.is_err() {
homestar_proc.kill().unwrap();
panic!("Homestar server/runtime failed to start in time");
}

Command::new(BIN.as_os_str())
.arg("ping")
.arg("--host")
.arg("127.0.0.1")
.arg("-p")
.arg("9999")
.assert()
.success()
.stdout(predicate::str::contains("127.0.0.1"))
.stdout(predicate::str::contains("pong"));

Ok(())
}

0 comments on commit 6bb32c9

Please sign in to comment.