From ea7e13b0c05bd03961c32aefca77a9cbf65eb1b2 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 30 Sep 2021 16:02:39 +0200 Subject: [PATCH] Begin teasing out general process specification schema --- syndicate-server/protocols/schema-bundle.bin | 2 +- .../protocols/schemas/externalServices.prs | 11 +- syndicate-server/src/services/daemon.rs | 151 ++++++++++-------- 3 files changed, 90 insertions(+), 74 deletions(-) diff --git a/syndicate-server/protocols/schema-bundle.bin b/syndicate-server/protocols/schema-bundle.bin index edc2ffe..bb1f6fb 100644 --- a/syndicate-server/protocols/schema-bundle.bin +++ b/syndicate-server/protocols/schema-bundle.bin @@ -2,4 +2,4 @@ ProcessDir´³orµµ±present´³dict·³dir´³named³dir´³atom³String„„„„„µ±invalid´³dict·³dir´³named³dir³any„„„„µ±absent´³dict·„„„„„³ ProcessEnv´³orµµ±present´³dict·³env´³named³env´³dictof´³refµ„³ EnvVariable„´³refµ„³EnvValue„„„„„„µ±invalid´³dict·³env´³named³env³any„„„„µ±absent´³dict·„„„„„³ CommandLine´³orµµ±shell´³atom³String„„µ±full´³refµ„³FullCommandLine„„„„³ EnvVariable´³orµµ±string´³atom³String„„µ±symbol´³atom³Symbol„„µ±invalid³any„„„³ FullProcess´³andµ´³dict·³argv´³named³argv´³refµ„³ CommandLine„„„„´³named³env´³refµ„³ ProcessEnv„„´³named³dir´³refµ„³ -ProcessDir„„´³named³clearEnv´³refµ„³ClearEnv„„´³named³ readyOnStart´³refµ„³ ReadyOnStart„„´³named³restart´³refµ„³ RestartField„„„„³ ReadyOnStart´³orµµ±present´³dict·³ readyOnStart´³named³ readyOnStart´³atom³Boolean„„„„„µ±invalid´³dict·³ readyOnStart´³named³ readyOnStart³any„„„„µ±absent´³dict·„„„„„³ RestartField´³orµµ±present´³dict·³restart´³named³restart´³refµ„³ RestartPolicy„„„„„µ±invalid´³dict·³restart´³named³restart³any„„„„µ±absent´³dict·„„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id³any„´³named³config´³refµ„³Process„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id³any„„„„³ RestartPolicy´³orµµ±always´³lit³always„„µ±onError´³lit³on-error„„µ±all´³lit³all„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³internalServices„´³schema·³version‘³ definitions·³ Milestone´³rec´³lit³ milestone„´³tupleµ´³named³name³any„„„„³ DebtReporter´³lit³ debt-reporter„³ ConfigWatcher´³rec´³lit³config-watcher„´³tupleµ´³named³path´³atom³String„„„„„³TcpRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„„„„³UnixRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Unix„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„ \ No newline at end of file +ProcessDir„„´³named³clearEnv´³refµ„³ClearEnv„„„„³ ReadyOnStart´³orµµ±present´³dict·³ readyOnStart´³named³ readyOnStart´³atom³Boolean„„„„„µ±invalid´³dict·³ readyOnStart´³named³ readyOnStart³any„„„„µ±absent´³dict·„„„„„³ RestartField´³orµµ±present´³dict·³restart´³named³restart´³refµ„³ RestartPolicy„„„„„µ±invalid´³dict·³restart´³named³restart³any„„„„µ±absent´³dict·„„„„„³ DaemonProcess´³rec´³lit³daemon„´³tupleµ´³named³id³any„´³named³config´³refµ„³DaemonProcessSpec„„„„„³ DaemonService´³rec´³lit³daemon„´³tupleµ´³named³id³any„„„„³ RestartPolicy´³orµµ±always´³lit³always„„µ±onError´³lit³on-error„„µ±all´³lit³all„„„„³FullCommandLine´³ tuplePrefixµ´³named³program´³atom³String„„„´³named³args´³seqof´³atom³String„„„„³DaemonProcessSpec´³orµµ±simple´³refµ„³ CommandLine„„µ±full´³refµ„³FullDaemonProcess„„„„³FullDaemonProcess´³andµ´³named³process´³refµ„³ FullProcess„„´³named³ readyOnStart´³refµ„³ ReadyOnStart„„´³named³restart´³refµ„³ RestartField„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„µ³internalServices„´³schema·³version‘³ definitions·³ Milestone´³rec´³lit³ milestone„´³tupleµ´³named³name³any„„„„³ DebtReporter´³lit³ debt-reporter„³ ConfigWatcher´³rec´³lit³config-watcher„´³tupleµ´³named³path´³atom³String„„„„„³TcpRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Tcp„„„„„³UnixRelayListener´³rec´³lit³relay-listener„´³tupleµ´³named³addr´³refµ³TransportAddress„³Unix„„„„„„³ embeddedType´³refµ³ EntityRef„³Cap„„„„„ \ No newline at end of file diff --git a/syndicate-server/protocols/schemas/externalServices.prs b/syndicate-server/protocols/schemas/externalServices.prs index 4280b2b..df95e86 100644 --- a/syndicate-server/protocols/schemas/externalServices.prs +++ b/syndicate-server/protocols/schemas/externalServices.prs @@ -4,7 +4,12 @@ embeddedType EntityRef.Cap . Service = DaemonService . DaemonService = . -DaemonProcess = . +DaemonProcess = . + +DaemonProcessSpec = @simple CommandLine / @full FullDaemonProcess . +FullDaemonProcess = @process FullProcess & @readyOnStart ReadyOnStart & @restart RestartField . +ReadyOnStart = @present { readyOnStart: bool } / @invalid { readyOnStart: any } / @absent {} . +RestartField = @present { restart: RestartPolicy } / @invalid { restart: any } / @absent {} . Process = @simple CommandLine / @full FullProcess . FullProcess = @@ -12,14 +17,10 @@ FullProcess = & @env ProcessEnv & @dir ProcessDir & @clearEnv ClearEnv - & @readyOnStart ReadyOnStart - & @restart RestartField . ProcessEnv = @present { env: { EnvVariable: EnvValue ...:... } } / @invalid { env: any } / @absent {} . ProcessDir = @present { dir: string } / @invalid { dir: any } / @absent {} . ClearEnv = @present { clearEnv: bool } / @invalid { clearEnv: any } / @absent {} . -ReadyOnStart = @present { readyOnStart: bool } / @invalid { readyOnStart: any } / @absent {} . -RestartField = @present { restart: RestartPolicy } / @invalid { restart: any } / @absent {} . CommandLine = @shell string / @full FullCommandLine . FullCommandLine = [@program string, @args string ...] . diff --git a/syndicate-server/src/services/daemon.rs b/syndicate-server/src/services/daemon.rs index e6e49af..66d60f4 100644 --- a/syndicate-server/src/services/daemon.rs +++ b/syndicate-server/src/services/daemon.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use syndicate::actor::*; use syndicate::enclose; -use syndicate::error::Error; use syndicate::supervise::{Supervisor, SupervisorConfiguration}; use tokio::process; @@ -30,10 +29,6 @@ pub fn on_demand(t: &mut Activation, config_ds: Arc, root_ds: Arc) { }); } -fn cannot_start() -> Result { - Err("Cannot start daemon process")? -} - impl Process { fn elaborate(self) -> FullProcess { match self { @@ -42,10 +37,83 @@ impl Process { env: ProcessEnv::Absent, dir: ProcessDir::Absent, clear_env: ClearEnv::Absent, + }, + Process::Full(spec) => *spec, + } + } +} + +impl FullProcess { + fn build_command(&self) -> Option { + let argv = self.argv.clone().elaborate(); + let mut cmd = process::Command::new(argv.program); + cmd.args(argv.args); + match &self.dir { + ProcessDir::Present { dir } => { cmd.current_dir(dir); () }, + ProcessDir::Absent => (), + ProcessDir::Invalid { dir } => { + tracing::error!(?dir, "Invalid working directory"); + return None; + } + } + match &self.clear_env { + ClearEnv::Present { clear_env: true } => { cmd.env_clear(); () }, + ClearEnv::Present { clear_env: false } => (), + ClearEnv::Absent => (), + ClearEnv::Invalid { clear_env } => { + tracing::error!(?clear_env, "Invalid clearEnv setting"); + return None; + } + } + match &self.env { + ProcessEnv::Present { env } => { + for (k, v) in env { + if let Some(env_variable) = match k { + EnvVariable::String(k) => Some(k), + EnvVariable::Symbol(k) => Some(k), + EnvVariable::Invalid(env_variable) => { + tracing::error!(?env_variable, + "Invalid environment variable name"); + return None; + } + } { + match v { + EnvValue::Set(value) => { cmd.env(env_variable, value); () } + EnvValue::Remove => { cmd.env_remove(env_variable); () } + EnvValue::Invalid(value) => { + tracing::error!(?env_variable, ?value, + "Invalid environment variable value"); + return None; + } + } + } + } + } + ProcessEnv::Absent => (), + ProcessEnv::Invalid { env } => { + tracing::error!(?env, "Invalid daemon environment"); + return None; + } + } + + cmd.stdin(std::process::Stdio::null()); + cmd.stdout(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); + cmd.kill_on_drop(true); + + Some(cmd) + } +} + +impl DaemonProcessSpec { + fn elaborate(self) -> FullDaemonProcess { + match self { + DaemonProcessSpec::Simple(command_line) => FullDaemonProcess { + process: Process::Simple(command_line).elaborate(), ready_on_start: ReadyOnStart::Absent, restart: RestartField::Absent, }, - Process::Full(spec) => *spec, + DaemonProcessSpec::Full(spec) => *spec, } } } @@ -62,7 +130,7 @@ impl CommandLine { } } -struct ProcessInstance { +struct DaemonInstance { name: tracing::Span, cmd: process::Command, announce_presumed_readiness: bool, @@ -71,7 +139,7 @@ struct ProcessInstance { restart_policy: RestartPolicy, } -impl ProcessInstance { +impl DaemonInstance { fn handle_exit(self, t: &mut Activation, error_message: Option) -> ActorResult { let delay = std::time::Duration::from_millis(if let None = error_message { 200 } else { 1000 }); @@ -184,68 +252,20 @@ fn run( counter::adjust(t, &unready_configs, 1); counter::adjust(t, &total_configs, 1); - match language().parse::(&config) { + match language().parse::(&config) { Ok(config) => { tracing::info!(?config); let config = config.elaborate(); let facet = t.facet.clone(); t.linked_task(syndicate::name!("subprocess"), async move { - let argv = config.argv.elaborate(); - let mut cmd = process::Command::new(argv.program); - cmd.args(argv.args); - match config.dir { - ProcessDir::Present { dir } => { cmd.current_dir(dir); () }, - ProcessDir::Absent => (), - ProcessDir::Invalid { dir } => { - tracing::error!(?dir, "Invalid working directory"); - return cannot_start(); - } - } - match config.clear_env { - ClearEnv::Present { clear_env: true } => { cmd.env_clear(); () }, - ClearEnv::Present { clear_env: false } => (), - ClearEnv::Absent => (), - ClearEnv::Invalid { clear_env } => { - tracing::error!(?clear_env, "Invalid clearEnv setting"); - return cannot_start(); - } - } - match config.env { - ProcessEnv::Present { env } => { - for (k, v) in env { - if let Some(env_variable) = match k { - EnvVariable::String(k) => Some(k), - EnvVariable::Symbol(k) => Some(k), - EnvVariable::Invalid(env_variable) => { - tracing::error!(?env_variable, - "Invalid environment variable name"); - return cannot_start(); - } - } { - match v { - EnvValue::Set(value) => { cmd.env(env_variable, value); () } - EnvValue::Remove => { cmd.env_remove(env_variable); () } - EnvValue::Invalid(value) => { - tracing::error!(?env_variable, ?value, - "Invalid environment variable value"); - return cannot_start(); - } - } - } - } - } - ProcessEnv::Absent => (), - ProcessEnv::Invalid { env } => { - tracing::error!(?env, "Invalid daemon environment"); - return cannot_start(); - } - } + let cmd = config.process.build_command().ok_or("Cannot start daemon process")?; + let announce_presumed_readiness = match config.ready_on_start { ReadyOnStart::Present { ready_on_start } => ready_on_start, ReadyOnStart::Absent => true, ReadyOnStart::Invalid { ready_on_start } => { tracing::error!(?ready_on_start, "Invalid readyOnStart value"); - return cannot_start(); + Err("Invalid readyOnStart value")? } }; let restart_policy = match config.restart { @@ -253,16 +273,11 @@ fn run( RestartField::Absent => RestartPolicy::All, RestartField::Invalid { restart } => { tracing::error!(?restart, "Invalid restart value"); - return cannot_start(); + Err("Invalid restart value")? } }; - cmd.stdin(std::process::Stdio::null()); - cmd.stdout(std::process::Stdio::inherit()); - cmd.stderr(std::process::Stdio::inherit()); - cmd.kill_on_drop(true); - - let process_instance = ProcessInstance { + let daemon_instance = DaemonInstance { name: tracing::Span::current(), cmd, announce_presumed_readiness, @@ -272,7 +287,7 @@ fn run( }; facet.activate(Account::new(syndicate::name!("instance-startup")), |t| { - process_instance.start(t) + daemon_instance.start(t) })?; Ok(LinkedTaskTermination::KeepFacet) });