Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/center.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
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.
Expand Down
151 changes: 79 additions & 72 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
4 changes: 2 additions & 2 deletions src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}

Expand Down
63 changes: 17 additions & 46 deletions src/state/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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.
Expand Down
21 changes: 1 addition & 20 deletions src/zone/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
io,
sync::{Arc, Mutex},
time::{Duration, SystemTime},
};
Expand All @@ -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},
};
Expand Down Expand Up @@ -403,23 +401,6 @@ impl Zone {
//--- Loading / Saving

impl Zone {
/// Reload the state of this zone.
pub fn reload_state(
self: &Arc<Self>,
policies: &mut foldhash::HashMap<Box<str>, 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
Expand Down
27 changes: 18 additions & 9 deletions src/zone/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize};
use tracing::warn;

use crate::{
loader::zone::LoaderState,
policy::{Policy, PolicyVersion},
zone::{Zone, ZoneState},
};
Expand All @@ -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<Zone>,
state: &mut ZoneState,
policies: &mut foldhash::HashMap<Box<str>, Policy>,
) {
) -> ZoneState {
/// Synchronize a loaded policy with global state.
fn sync_policy(
policy: PolicyVersion,
Expand Down Expand Up @@ -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()
}
}
}
}
Expand Down
Loading