Skip to content

Commit

Permalink
Feat: Add capture for windows signals (#234)
Browse files Browse the repository at this point in the history
# Description

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.

## Link to issue

Fixes: [#183]

## Type of change
- [X] New feature (non-breaking change that adds functionality)

Please delete options that are not relevant.

## Test plan (required)
Added a few tests into the CLI around testing both windows and
non-windows functionality. Includes some ipv4 testing, daemon testing
and windows signal testing.
  • Loading branch information
euamcg authored Aug 9, 2023
1 parent 46cc979 commit cee37d4
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 15 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, macos-latest]
if: >
github.ref == 'refs/heads/main' &&
github.repository_owner == 'ipvm-wg' &&
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/tests_and_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
rust-toolchain:
- stable
- nightly
# minimum version
- 1.67
# 1.68 fixes: https://github.com/rust-lang/cargo/pull/11347
- 1.68
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repository
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ authors = [
]
edition = "2021"
license = "Apache"
rust-version = "1.67.0"
rust-version = "1.68"

[workspace.dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
Expand Down
9 changes: 9 additions & 0 deletions homestar-runtime/fixtures/test_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"))
}
21 changes: 20 additions & 1 deletion 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 @@ -416,6 +419,7 @@ impl Runner {

/// Captures shutdown signals for [Runner].
#[allow(dead_code)]
#[cfg(not(windows))]
async fn shutdown_signal() -> Result<()> {
let mut sigint = signal(SignalKind::interrupt())?;
let mut sigterm = signal(SignalKind::terminate())?;
Expand All @@ -425,7 +429,22 @@ impl Runner {
_ = sigint.recv() => info!("SIGINT received, shutting down"),
_ = sigterm.recv() => info!("SIGTERM received, shutting down"),
}
Ok(())
}

#[allow(dead_code)]
#[cfg(windows)]
async fn shutdown_signal() -> Result<()> {
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
188 changes: 179 additions & 9 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},
net::{IpAddr, Ipv4Addr, 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 @@ -87,15 +87,21 @@ fn test_server_not_running_serial() -> Result<()> {
.arg("ping")
.assert()
.failure()
.stderr(predicate::str::contains("Connection refused"));
.stderr(
predicate::str::contains("Connection refused")
.or(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("Connection refused"));
.stderr(
predicate::str::contains("Connection refused")
.or(predicate::str::contains("No connection could be made")),
);

Command::new(BIN.as_os_str())
.arg("ping")
Expand All @@ -105,7 +111,8 @@ fn test_server_not_running_serial() -> Result<()> {
.failure()
.stderr(
predicate::str::contains("No route to host")
.or(predicate::str::contains("Network is unreachable")),
.or(predicate::str::contains("Network is unreachable")
.or(predicate::str::contains("unreachable network"))),
);

Command::new(BIN.as_os_str())
Expand All @@ -114,7 +121,8 @@ fn test_server_not_running_serial() -> Result<()> {
.failure()
.stderr(
predicate::str::contains("Connection refused")
.or(predicate::str::contains("server was already shutdown")),
.or(predicate::str::contains("server was already shutdown")
.or(predicate::str::contains("No connection could be made"))),
);
let _ = stop_bin();

Expand Down Expand Up @@ -164,7 +172,10 @@ fn test_server_serial() -> Result<()> {
.arg("9999")
.assert()
.failure()
.stderr(predicate::str::contains("Connection refused"));
.stderr(
predicate::str::contains("Connection refused")
.or(predicate::str::contains("No connection could be made")),
);

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

Expand Down Expand Up @@ -192,7 +203,7 @@ fn test_workflow_run_serial() -> Result<()> {
.arg("start")
.arg("--db")
.arg("homestar.db")
//.stdout(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();

Expand Down Expand Up @@ -257,6 +268,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 +323,159 @@ fn test_daemon_serial() -> Result<()> {

Ok(())
}

#[test]
#[serial]
#[cfg(windows)]
fn test_signal_kill_serial() -> 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_v4_serial() -> Result<()> {
let _ = stop_bin();

let mut homestar_proc = Command::new(BIN.as_os_str())
.arg("start")
.arg("-c")
.arg("fixtures/test_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"));

let _ = stop_bin();

Ok(())
}

#[test]
#[serial]
#[cfg(not(windows))]
fn test_daemon_v4_serial() -> Result<()> {
use std::fs;
use sysinfo::PidExt;

let _ = stop_bin();

Command::new(BIN.as_os_str())
.arg("start")
.arg("-c")
.arg("fixtures/test_v4.toml")
.arg("-d")
.env("DATABASE_URL", "homestar.db")
.stdout(Stdio::piped())
.assert()
.success();

let system = sysinfo::System::new_all();
let pid = system
.processes_by_exact_name("homestar-runtime")
.collect::<Vec<_>>()
.first()
.map(|p| p.pid().as_u32())
.unwrap_or(
fs::read_to_string("/tmp/homestar.pid")
.expect("Should have a PID file")
.trim()
.parse::<u32>()
.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() {
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"));

let _result = signal::kill(Pid::from_raw(pid.try_into().unwrap()), Signal::SIGTERM);
let _result = retry(Fixed::from_millis(500), || {
Command::new(BIN.as_os_str())
.arg("ping")
.assert()
.try_failure()
});

let _ = stop_bin();

Ok(())
}

0 comments on commit cee37d4

Please sign in to comment.