Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

windows health checks run from a single powershell instance #6947

Merged
merged 1 commit into from Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions build.ps1
Expand Up @@ -95,11 +95,11 @@ function Invoke-Build([string]$Path, [switch]$Clean, [string]$Command, [switch]$
break
}
"clippy" {
& $PSScriptRoot\test\run_clippy.ps1 -ToolChain $toolchain `
-UnexaminedLintsPath $PSScriptRoot\test\unexamined_lints.txt `
-AllowedLintsPath $PSScriptRoot\test\allowed_lints.txt `
-LintsToFixPath $PSScriptRoot\test\lints_to_fix.txt `
-DeniedLintsPath $PSScriptRoot\test\denied_lints.txt `
& $PSScriptRoot\.expeditor\scripts\verify\run_clippy.ps1 -ToolChain $toolchain `
-UnexaminedLintsPath $PSScriptRoot\support\unexamined_lints.txt `
-AllowedLintsPath $PSScriptRoot\support\allowed_lints.txt `
-LintsToFixPath $PSScriptRoot\support\lints_to_fix.txt `
-DeniedLintsPath $PSScriptRoot\support\denied_lints.txt `
break
}
"test" {
Expand Down
5 changes: 5 additions & 0 deletions components/common/src/error.rs
Expand Up @@ -41,6 +41,7 @@ pub enum Error {
/// Errors when joining paths :)
JoinPathsError(env::JoinPathsError),
MissingCLIInputError(String),
NamedPipeTimeoutOnStart(String, String, io::Error),
NetParseError(net::AddrParseError),
OfflineArtifactNotFound(PackageIdent),
OfflineOriginKeyNotFound(String),
Expand Down Expand Up @@ -114,6 +115,10 @@ impl fmt::Display for Error {
}
Error::IO(ref err) => format!("{}", err),
Error::JoinPathsError(ref err) => format!("{}", err),
Error::NamedPipeTimeoutOnStart(ref group, ref hook, ref err) => {
format!("Unable to start powershell named pipe for {} hook of {}: {}",
hook, group, err)
}
Error::NetParseError(ref err) => format!("{}", err),
Error::OfflineArtifactNotFound(ref ident) => {
format!("Cached artifact not found in offline mode: {}", ident)
Expand Down
2 changes: 1 addition & 1 deletion components/common/src/templating/hooks.rs
Expand Up @@ -440,7 +440,7 @@ pub struct HookOutput<'a> {
}

impl<'a> HookOutput<'a> {
fn new(stdout_log: &'a Path, stderr_log: &'a Path) -> Self {
pub fn new(stdout_log: &'a Path, stderr_log: &'a Path) -> Self {
Self { stdout_log_file: stdout_log,
stderr_log_file: stderr_log, }
}
Expand Down
6 changes: 4 additions & 2 deletions components/core/src/os/process/windows.rs
Expand Up @@ -12,7 +12,8 @@ use winapi::{shared::minwindef::{DWORD,
processthreadsapi,
winnt::{HANDLE,
PROCESS_QUERY_LIMITED_INFORMATION,
PROCESS_TERMINATE}}};
PROCESS_TERMINATE,
SYNCHRONIZE}}};

const STILL_ACTIVE: u32 = 259;

Expand All @@ -28,7 +29,8 @@ pub fn current_pid() -> u32 { unsafe { processthreadsapi::GetCurrentProcessId()
pub fn handle_from_pid(pid: Pid) -> Option<HANDLE> {
unsafe {
let proc_handle = processthreadsapi::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION
| PROCESS_TERMINATE,
| PROCESS_TERMINATE
| SYNCHRONIZE,
FALSE,
pid);

Expand Down
5 changes: 4 additions & 1 deletion components/sup/Cargo.toml
Expand Up @@ -76,7 +76,10 @@ jemalloc-ctl = "*"

[target.'cfg(windows)'.dependencies]
ctrlc = "*"
winapi = { version = "*", features = ["tlhelp32"] }
mio-named-pipes = "*"
mio = "*"
uuid = { version = "*", features = ["v4"] }
winapi = { version = "*", features = ["namedpipeapi", "tlhelp32"] }

[dev-dependencies]
habitat_core = { path = "../core" }
Expand Down
1 change: 1 addition & 0 deletions components/sup/habitat/plan.ps1
Expand Up @@ -78,6 +78,7 @@ function Invoke-Build {

function Invoke-Install {
Copy-Item "$env:CARGO_TARGET_DIR/release/hab-sup.exe" "$pkg_prefix/bin/hab-sup.exe"
Copy-Item "$PLAN_CONTEXT/../static/named_pipe_service.ps1" "$pkg_prefix/bin/named_pipe_service.ps1"
Copy-Item "$(Get-HabPackagePath "openssl")/bin/*.dll" "$pkg_prefix/bin"
Copy-Item "$(Get-HabPackagePath "zlib")/bin/*.dll" "$pkg_prefix/bin"
Copy-Item "$(Get-HabPackagePath "libarchive")/bin/*.dll" "$pkg_prefix/bin"
Expand Down
2 changes: 2 additions & 0 deletions components/sup/src/manager/service.rs
Expand Up @@ -12,6 +12,8 @@ mod context;
mod health;
mod hook_runner;
mod hooks;
#[cfg(windows)]
mod pipe_hook_client;
mod spawned_future;
pub mod spec;
mod supervisor;
Expand Down
162 changes: 120 additions & 42 deletions components/sup/src/manager/service/hooks.rs
@@ -1,3 +1,5 @@
#[cfg(windows)]
use super::pipe_hook_client::PipeHookClient;
use habitat_common::{error::Result,
outputln,
templating::{hooks::{self,
Expand Down Expand Up @@ -82,9 +84,12 @@ impl Hook for FileUpdatedHook {

#[derive(Debug, Serialize)]
pub struct HealthCheckHook {
render_pair: RenderPair,
render_pair: RenderPair,
stdout_log_path: PathBuf,
stderr_log_path: PathBuf,
#[cfg(windows)]
#[serde(skip_serializing)]
pipe_client: PipeHookClient,
}

impl Hook for HealthCheckHook {
Expand All @@ -93,9 +98,42 @@ impl Hook for HealthCheckHook {
fn file_name() -> &'static str { "health-check" }

fn new(package_name: &str, pair: RenderPair) -> Self {
HealthCheckHook { render_pair: pair,
stdout_log_path: hooks::stdout_log_path::<Self>(package_name),
stderr_log_path: hooks::stderr_log_path::<Self>(package_name), }
let out_path = hooks::stdout_log_path::<Self>(package_name);
let err_path = hooks::stderr_log_path::<Self>(package_name);
#[cfg(windows)]
let path = pair.path.to_path_buf();
mwrock marked this conversation as resolved.
Show resolved Hide resolved
HealthCheckHook { render_pair: pair,
stdout_log_path: out_path.clone(),
stderr_log_path: err_path.clone(),
#[cfg(windows)]
pipe_client:
PipeHookClient::new(Self::file_name().to_string(),
path,
out_path,
err_path), }
}

#[cfg(windows)]
fn run<T>(&self,
service_group: &str,
pkg: &Pkg,
svc_encrypted_password: Option<T>)
-> Result<Self::ExitValue>
where T: ToString
{
match self.pipe_client
.exec_hook(service_group, pkg, svc_encrypted_password)
{
Ok(exit) => {
let hook_output = HookOutput::new(self.stdout_log_path(), self.stderr_log_path());
Ok(self.handle_exit(pkg, &hook_output, ExitStatus::from(exit)))
}
Err(err) => {
outputln!(preamble service_group,
"Hook failed to run, {}, {}", Self::file_name(), err);
Err(err)
}
}
}

fn handle_exit<'a>(&self,
Expand Down Expand Up @@ -617,13 +655,15 @@ mod tests {
service_file::ServiceFile as ServiceFileRumor,
RumorStore}};
use habitat_common::{cli::FS_ROOT,
locked_env_var,
templating::{config::Cfg,
package::Pkg,
test_helpers::*},
types::{GossipListenAddr,
HttpListenAddr,
ListenCtlAddr}};
use habitat_core::{fs::cache_key_path,
use habitat_core::{fs::{cache_key_path,
svc_logs_path},
package::{PackageIdent,
PackageInstall},
service::{ServiceBind,
Expand Down Expand Up @@ -671,46 +711,24 @@ mod tests {
ServiceGroup")
}

////////////////////////////////////////////////////////////////////////

#[test]
fn compile_hook_table() {
let tmp_root = rendered_hooks_path();
let hooks_path = tmp_root.path().join("hooks");
fs::create_dir_all(&hooks_path).unwrap();

let service_group = service_group();

let concrete_path = hooks_path.clone(); // rendered_hooks_path();
let template_path = hook_templates_path();

////////////////////////////////////////////////////////////////////////
// BEGIN RENDER CONTEXT SETUP
// (See comment above)

let sys = Sys::new(true,
GossipListenAddr::default(),
ListenCtlAddr::default(),
HttpListenAddr::default()).expect("to create Sys");

fn pkg(service_group: &ServiceGroup) -> Pkg {
let pg_id = PackageIdent::new("testing",
&service_group.service(),
service_group.service(),
Some("1.0.0"),
Some("20170712000000"));

let pkg_install = PackageInstall::new_from_parts(pg_id.clone(),
PathBuf::from("/tmp"),
PathBuf::from("/tmp"),
PathBuf::from("/tmp"));
let pkg = Pkg::from_install(&pkg_install).expect("Could not create package!");

// This is gross, but it actually works
let cfg_path = &concrete_path.as_path().join("default.toml");
create_with_content(cfg_path, "message = \"Hello\"");

let cfg = Cfg::new(&pkg, Some(&concrete_path.as_path().to_path_buf()))
.expect("Could not create config");
Pkg::from_install(&pkg_install).unwrap()
}

fn ctx<'a>(service_group: &'a ServiceGroup,
pkg: &'a Pkg,
sys: &'a Sys,
cfg: &'a Cfg,
ring: &'a mut CensusRing)
-> RenderContext<'a> {
// SysInfo is basic Swim infrastructure information
let mut sys_info = SysInfo::default();
sys_info.ip = "1.2.3.4".to_string();
Expand All @@ -723,7 +741,7 @@ mod tests {
let sg_one = service_group.clone(); // ServiceGroup::new("shield", "one", None).unwrap();

let service_store: RumorStore<ServiceRumor> = RumorStore::default();
let service_one = ServiceRumor::new("member-a", &pg_id, sg_one.clone(), sys_info, None);
let service_one = ServiceRumor::new("member-a", &pkg.ident, sg_one.clone(), sys_info, None);
service_store.insert_rsw(service_one);

let election_store: RumorStore<ElectionRumor> = RumorStore::default();
Expand All @@ -742,7 +760,6 @@ mod tests {
let service_config_store: RumorStore<ServiceConfigRumor> = RumorStore::default();
let service_file_store: RumorStore<ServiceFileRumor> = RumorStore::default();

let mut ring = CensusRing::new("member-a");
ring.update_from_rumors_rsr_mlr(&cache_key_path(Some(&*FS_ROOT)),
&service_store,
&election_store,
Expand All @@ -753,10 +770,33 @@ mod tests {

let bindings = iter::empty::<&ServiceBind>();

let ctx = RenderContext::new(&service_group, &sys, &pkg, &cfg, &ring, bindings);
RenderContext::new(service_group, sys, pkg, cfg, ring, bindings)
}

////////////////////////////////////////////////////////////////////////

// END RENDER CONTEXT SETUP
////////////////////////////////////////////////////////////////////////
#[test]
fn compile_hook_table() {
let tmp_root = rendered_hooks_path();
let hooks_path = tmp_root.path().join("hooks");
fs::create_dir_all(&hooks_path).unwrap();
let service_group = service_group();
let concrete_path = hooks_path.clone(); // rendered_hooks_path();
let template_path = hook_templates_path();

// This is gross, but it actually works
let cfg_path = &concrete_path.as_path().join("default.toml");
create_with_content(cfg_path, "message = \"Hello\"");

let pkg = pkg(&service_group);
let sys = Sys::new(true,
GossipListenAddr::default(),
ListenCtlAddr::default(),
HttpListenAddr::default()).expect("to create Sys");
let cfg = Cfg::new(&pkg, Some(&concrete_path.as_path().to_path_buf()))
.expect("Could not create config");
let mut ring = CensusRing::new("member-a");
let ctx = ctx(&service_group, &pkg, &sys, &cfg, &mut ring);

let hook_table = HookTable::load(&service_group, &template_path, &hooks_path);
assert!(hook_table.compile(&service_group, &ctx).changed());
Expand Down Expand Up @@ -800,4 +840,42 @@ mod tests {
let result = SuitabilityHook::parse_suitability(reader, "test_pkg_name");
assert_eq!(result.unwrap(), 124);
}

locked_env_var!(HAB_HOOK_PIPE_SCRIPT, pipe_service_path);

#[cfg(windows)]
#[test]
fn run_named_pipe_health_check_hook() {
let var = pipe_service_path();
let script = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("static")
.join("named_pipe_service.ps1");
var.set(&script);

let service_group = service_group();
let tmp_root = rendered_hooks_path().into_path();
let hooks_path = tmp_root.clone().join("hooks");
fs::create_dir_all(&hooks_path).unwrap();
fs::create_dir_all(svc_logs_path(&service_group.service())).unwrap();
let concrete_path = hooks_path.clone();
let template_path = hook_templates_path();

let hook = HealthCheckHook::load(&service_group.service(), &concrete_path, &template_path)
.expect("Could not create testing healch-check hook");

let pkg = pkg(&service_group);
let sys = Sys::new(true,
GossipListenAddr::default(),
ListenCtlAddr::default(),
HttpListenAddr::default()).expect("to create Sys");
let cfg = Cfg::new(&pkg, Some(&concrete_path.as_path().to_path_buf()))
.expect("Could not create config");
let mut ring = CensusRing::new("member-a");
let ctx = ctx(&service_group, &pkg, &sys, &cfg, &mut ring);

hook.compile(&service_group, &ctx).unwrap();

let result = hook.run(&service_group, &pkg, None::<&str>).unwrap();

assert_eq!(Some(1), result.exit_status().code());
}
}