diff --git a/src/center.rs b/src/center.rs index 8a82b6cd2..24535150c 100644 --- a/src/center.rs +++ b/src/center.rs @@ -325,11 +325,10 @@ pub struct State { impl State { /// Attempt to load the global state file. - pub fn init_from_file(&mut self, config: &Config) -> io::Result<()> { + pub fn init_from_file(config: &Config) -> io::Result { let path = config.daemon.state_file.value(); let spec = crate::state::Spec::load(path)?; - spec.parse_into(self); - Ok(()) + Ok(spec.parse()) } /// Mark the global state as dirty. diff --git a/src/main.rs b/src/main.rs index 1bba546d5..6d9a963bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -74,88 +74,95 @@ fn main() -> ExitCode { } // Load the global state file or build one from scratch. - let mut state = center::State::default(); - if let Err(err) = state.init_from_file(&config) { - if err.kind() != io::ErrorKind::NotFound { - error!("Could not load the state file: {err}"); - return ExitCode::FAILURE; - } - - info!("State file not found; starting from scratch"); - - // Create required subdirectories (and their parents) if they don't - // exist. This is only needed for directories to which we write files - // without using util::write_file() as that function creates the - // directory (and parent directories) if missing. However, do it for - // all state directories now so that we don't discover only later that - // we can't create the directory. - // TODO: Once we implement live config reloading, this should move - // somewhere else to also create the directories as specified in a the - // reloaded config. - for dir in [ - &*config.keys_dir, - config.kmip_credentials_store_path.parent().unwrap(), - &*config.kmip_server_state_dir, - &*config.policy_dir, - &*config.zone_state_dir, - ] { - if let Err(e) = create_dir_all(dir) { - error!("Unable to create directory '{dir}': {e}",); + let mut state = match center::State::init_from_file(&config) { + Err(err) => { + if err.kind() != io::ErrorKind::NotFound { + error!("Could not load the state file: {err}"); return ExitCode::FAILURE; - }; - } + } - // Load all policies. - let mut updates = Vec::new(); - let res = policy::reload_all(&mut state.policies, &config, |name, _| { - updates.push(name.clone()); - }); + info!("State file not found; starting from scratch"); + + // Create required subdirectories (and their parents) if they don't + // exist. This is only needed for directories to which we write files + // without using util::write_file() as that function creates the + // directory (and parent directories) if missing. However, do it for + // all state directories now so that we don't discover only later that + // we can't create the directory. + // TODO: Once we implement live config reloading, this should move + // somewhere else to also create the directories as specified in a the + // reloaded config. + for dir in [ + &*config.keys_dir, + config.kmip_credentials_store_path.parent().unwrap(), + &*config.kmip_server_state_dir, + &*config.policy_dir, + &*config.zone_state_dir, + ] { + if let Err(e) = create_dir_all(dir) { + error!("Unable to create directory '{dir}': {e}",); + return ExitCode::FAILURE; + }; + } - if let Err(err) = res { - error!("Cascade couldn't load all policies: {err}"); - return ExitCode::FAILURE; - } + let mut state = center::State::default(); - for name in updates { - let pol = state - .policies - .get(&name) - .expect("we just reloaded these policies"); + // Load all policies. + let mut updates = Vec::new(); + let res = policy::reload_all(&mut state.policies, &config, |name, _| { + updates.push(name.clone()); + }); - for zone_name in &pol.zones { - let zone = state - .zones - .get(zone_name) - .expect("zones and policies are consistent"); + if let Err(err) = res { + error!("Cascade couldn't load all policies: {err}"); + return ExitCode::FAILURE; + } - let mut state = zone.0.state.lock().expect("lock isn't poisoned"); - state.policy = Some(pol.latest.clone()); + for name in updates { + let pol = state + .policies + .get(&name) + .expect("we just reloaded these policies"); + + for zone_name in &pol.zones { + let zone = state + .zones + .get(zone_name) + .expect("zones and policies are consistent"); + + let mut state = zone.0.state.lock().expect("lock isn't poisoned"); + state.policy = Some(pol.latest.clone()); + } } + + // TODO: Fail if any zone state files exist. + state } + Ok(mut state) => { + info!("Successfully loaded the global state file"); + + let zone_state_dir = &config.zone_state_dir; + let policies = &mut state.policies; + for zone in &state.zones { + let name = &zone.0.name; + let path = zone_state_dir.join(format!("{name}.db")); + let spec = match cascaded::zone::state::Spec::load(&path) { + Ok(spec) => { + debug!("Loaded state of zone '{name}' (from {path})"); + spec + } + Err(err) => { + error!("Failed to load zone state '{name}' from '{path}': {err}"); + return ExitCode::FAILURE; + } + }; + let mut state = zone.0.state.lock().unwrap(); + *state = spec.parse(&zone.0, policies); + } - // TODO: Fail if any zone state files exist. - } else { - info!("Successfully loaded the global state file"); - - let zone_state_dir = &config.zone_state_dir; - let policies = &mut state.policies; - for zone in &state.zones { - let name = &zone.0.name; - let path = zone_state_dir.join(format!("{name}.db")); - let spec = match cascaded::zone::state::Spec::load(&path) { - Ok(spec) => { - debug!("Loaded state of zone '{name}' (from {path})"); - spec - } - Err(err) => { - error!("Failed to load zone state '{name}' from '{path}': {err}"); - return ExitCode::FAILURE; - } - }; - let mut state = zone.0.state.lock().unwrap(); - spec.parse_into(&zone.0, &mut state, policies); + state } - } + }; if config.loader.review.servers.is_empty() { warn!( diff --git a/src/state/mod.rs b/src/state/mod.rs index a78fbc79b..e5b896d75 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -53,9 +53,9 @@ pub enum Spec { impl Spec { /// Parse from this specification. - pub fn parse_into(self, state: &mut State) { + pub fn parse(self) -> State { match self { - Self::V1(spec) => spec.parse_into(state), + Self::V1(spec) => spec.parse(), } } diff --git a/src/state/v1.rs b/src/state/v1.rs index 51d4a1f77..a0cf2b317 100644 --- a/src/state/v1.rs +++ b/src/state/v1.rs @@ -6,10 +6,11 @@ use bytes::Bytes; use domain::base::Name; use domain::base::Ttl; use serde::{Deserialize, Serialize}; -use tracing::{error, info, trace}; +use tracing::info; use crate::policy::file::v1::OutboundSpec; use crate::policy::{AutoConfig, DsAlgorithm, KeyParameters}; +use crate::tsig::TsigStore; use crate::{ center::State, policy::{ @@ -39,61 +40,31 @@ pub struct Spec { impl Spec { /// Parse from this specification. - pub fn parse_into(self, state: &mut State) { - // TODO: There may be interdependencies between zones and policies - // (e.g. if a removed policy was being used by a removed zone), so we - // can't just update them one after the other. - - // Update the policy set. - let mut new_policies = foldhash::HashMap::default(); + pub fn parse(self) -> State { + let mut policies = foldhash::HashMap::default(); for (name, spec) in self.policies { - let policy = match state.policies.remove(&name) { - Some(mut policy) => { - trace!("Retaining existing policy '{name}'"); - spec.parse_into(&mut policy); - policy - } - None => { - info!("Adding policy '{name}' from global state"); - spec.parse(&name) - } - }; - new_policies.insert(name, policy); - } - for (name, policy) in state.policies.drain() { - if !policy.zones.is_empty() { - error!( - "The policy '{name}' has been removed from the global state, but some zones are still using it; Cascade will preserve its internal copy" - ); - new_policies.insert(name, policy); - } else { - info!("Removing policy '{name}'"); - } + info!("Adding policy '{name}' from global state"); + let policy = spec.parse(&name); + policies.insert(name, policy); } - state.policies = new_policies; - // Update the zone set. #[allow(clippy::mutable_key_type)] - let new_zones = self + let zones = self .zones .into_iter() - .map(|name| match state.zones.take(&name) { - Some(zone) => { - trace!("Retaining existing zone '{name}'"); - zone - } - None => { - info!("Adding zone '{name}' from global state"); - ZoneByName(Arc::new(Zone::new(name.clone()))) - } + .map(|name| { + info!("Adding zone '{name}' from global state"); + ZoneByName(Arc::new(Zone::new(name.clone()))) }) .collect(); - for zone in state.zones.drain() { - info!("Removing zone '{}'", zone.0.name); + State { + zones, + policies, + rt_config: cascade_cfg::RuntimeConfig::default(), + tsig_store: TsigStore::default(), + enqueued_save: None, } - - state.zones = new_zones; } /// Build this state specification. diff --git a/src/zone/mod.rs b/src/zone/mod.rs index bb89f58f2..ed056deb9 100644 --- a/src/zone/mod.rs +++ b/src/zone/mod.rs @@ -5,7 +5,6 @@ use std::{ cmp::Ordering, fmt, hash::{Hash, Hasher}, - io, sync::{Arc, Mutex}, time::{Duration, SystemTime}, }; @@ -19,9 +18,8 @@ use tracing::{debug, error, trace}; use crate::{ api::{self, ZoneReviewStatus}, center::Center, - config::Config, loader::zone::{LoaderState, LoaderZoneHandle}, - policy::{Policy, PolicyVersion}, + policy::PolicyVersion, signer::zone::{SignerState, SignerZoneHandle}, util::{deserialize_duration_from_secs, serialize_duration_as_secs}, }; @@ -403,23 +401,6 @@ impl Zone { //--- Loading / Saving impl Zone { - /// Reload the state of this zone. - pub fn reload_state( - self: &Arc, - policies: &mut foldhash::HashMap, Policy>, - config: &Config, - ) -> io::Result<()> { - // Load and parse the state file. - let path = config.zone_state_dir.join(format!("{}.db", self.name)); - let spec = state::Spec::load(&path)?; - - // Merge the parsed data. - let mut state = self.state.lock().unwrap(); - spec.parse_into(self, &mut state, policies); - - Ok(()) - } - /// Mark the zone as dirty. /// /// A persistence operation for the zone will be enqueued (unless one diff --git a/src/zone/state/mod.rs b/src/zone/state/mod.rs index c870bbb51..23c1b204d 100644 --- a/src/zone/state/mod.rs +++ b/src/zone/state/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use tracing::warn; use crate::{ + loader::zone::LoaderState, policy::{Policy, PolicyVersion}, zone::{Zone, ZoneState}, }; @@ -32,12 +33,11 @@ pub enum Spec { impl Spec { /// Merge this specification with an existing zone state. - pub fn parse_into( + pub fn parse( self, zone: &Arc, - state: &mut ZoneState, policies: &mut foldhash::HashMap, Policy>, - ) { + ) -> ZoneState { /// Synchronize a loaded policy with global state. fn sync_policy( policy: PolicyVersion, @@ -88,19 +88,28 @@ impl Spec { next_min_expiration, history, }) => { - state.policy = policy.map(|policy| sync_policy(policy.parse(), zone, policies)); - state.loader.source = source.parse(); - state.min_expiration = min_expiration; - state.next_min_expiration = next_min_expiration; - state.history = history; + let loader = LoaderState { + source: source.parse(), + ..Default::default() + }; // This should always be some at this stage... - if let Some(ref policy) = state.policy { + let policy = policy.map(|policy| sync_policy(policy.parse(), zone, policies)); + if let Some(policy) = &policy { let p = policies .get_mut(&*policy.name) .expect("zone policy references should not be kept around"); p.zones.insert(zone.name.clone()); } + + ZoneState { + policy, + min_expiration, + next_min_expiration, + loader, + history, + ..Default::default() + } } } }