Skip to content

Commit

Permalink
Generate chrony config
Browse files Browse the repository at this point in the history
  • Loading branch information
karencfv committed Apr 2, 2024
1 parent 746f36a commit 44e5a91
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 34 deletions.
2 changes: 0 additions & 2 deletions smf/ntp/etc/inet/chrony.conf.boundary
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
# NTP servers outside the rack.
#

pool @SERVER@ iburst maxdelay 0.1 maxsources 16

driftfile /var/lib/chrony/drift
ntsdumpdir /var/lib/chrony
dumpdir /var/lib/chrony
Expand Down
2 changes: 0 additions & 2 deletions smf/ntp/etc/inet/chrony.conf.internal
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
# boundary NTP servers within the rack.
#

server @SERVER@ iburst minpoll 0 maxpoll 4

driftfile /var/lib/chrony/drift
ntsdumpdir /var/lib/chrony
dumpdir /var/lib/chrony
Expand Down
149 changes: 119 additions & 30 deletions zone-setup/src/bin/zone-setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,33 @@
//! CLI to set up zone networking
use anyhow::anyhow;
use clap::{arg, command, Arg, ArgMatches, Command};
use clap::{arg, command, value_parser, Arg, ArgMatches, Command};
use illumos_utils::ipadm::Ipadm;
use illumos_utils::route::{Gateway, Route};
use omicron_common::cmd::fatal;
use omicron_common::cmd::CmdError;
use slog::{info, Logger};
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

pub const HOSTS_FILE: &str = "/etc/inet/hosts";
//pub const CHRONY_CONFIG_FILE: &str = "/etc/inet/chrony.conf";
//pub const INTERNAL_NTP_CONFIG_TPL: &str = "/etc/inet/chrony.conf.internal";
//pub const BOUNDARY_NTP_CONFIG_TPL: &str = "/etc/inet/chrony.conf.boundary";

// TODO: Removeme, only for testing locally
pub const CHRONY_CONFIG_FILE: &str = "./smf/ntp/etc/inet/chrony.conf";
pub const INTERNAL_NTP_CONFIG_TPL: &str =
"./smf/ntp/etc/inet/chrony.conf.internal";
pub const BOUNDARY_NTP_CONFIG_TPL: &str =
"./smf/ntp/etc/inet/chrony.conf.boundary";

pub const COMMON_NW_CMD: &str = "common-networking";
pub const OPTE_INTERFACE_CMD: &str = "opte-interface";
pub const NTP_CMD: &str = "ntp";
pub const NTP_START_CMD: &str = "start";

fn parse_ip(s: &str) -> anyhow::Result<IpAddr> {
if s == "unknown" {
Expand Down Expand Up @@ -60,16 +77,6 @@ fn parse_chrony_conf(s: &str) -> anyhow::Result<String> {
s.parse().map_err(|_| anyhow!("ERROR: Invalid chrony configuration file"))
}

fn parse_chrony_tpl(s: &str) -> anyhow::Result<String> {
if s == "unknown" {
return Err(anyhow!("ERROR: Missing chrony template"));
};

// TODO: actually check the format of the string matches one of internal
// or boundary NTP
s.parse().map_err(|_| anyhow!("ERROR: Invalid chrony template"))
}

fn parse_boundary(s: &str) -> anyhow::Result<bool> {
s.parse().map_err(|_| anyhow!("ERROR: Invalid boundary input"))
}
Expand All @@ -92,7 +99,7 @@ async fn do_run() -> Result<(), CmdError> {

let matches = command!()
.subcommand(
Command::new("common-networking")
Command::new(COMMON_NW_CMD)
.about(
"Sets up common networking configuration across all zones",
)
Expand All @@ -119,7 +126,7 @@ async fn do_run() -> Result<(), CmdError> {
),
)
.subcommand(
Command::new("opte-interface")
Command::new(OPTE_INTERFACE_CMD)
.about("Sets up OPTE interface")
.arg(
arg!(
Expand All @@ -144,22 +151,16 @@ async fn do_run() -> Result<(), CmdError> {
),
)
.subcommand(
Command::new("ntp")
Command::new(NTP_CMD)
.about("Start (set up), refresh and stop methods for NTP zone")
.subcommand(
Command::new("start")
.about("NTP zone setup and start chronyd daemon")
// TODO: Maybe I don't even need --file nor --template
.arg(
arg!(-f --file <String> "Chrony configuration file")
.required(true)
.default_value(CHRONY_CONFIG_FILE)
.value_parser(parse_chrony_conf)
)
.arg(
arg!(-t --template <String> "Boundary or internal NTP configuration template")
.required(true)
.value_parser(parse_chrony_tpl),
)
.arg(
arg!(-b --boundary <bool> "Whether this is a boundary or internal NTP zone")
.required(true)
Expand All @@ -169,32 +170,120 @@ async fn do_run() -> Result<(), CmdError> {
Arg::new("servers")
.short('s')
.long("servers")
.num_args(1..)
.value_delimiter(' ')
.value_parser(value_parser!(String))
.help("List of NTP servers separated by a space")
.required(true)
// TODO: Add some parsing to this?
)
.arg(
Arg::new("allow")
.short('a')
.long("allow")
.value_delimiter(' ')
.help("List of allowed IPv6 addresses separated by a space")
// Not required as this only appears in boundary NTP config
// TODO: Add some parsing to this?
arg!(-a --allow <Ipv6Addr> "Allowed IPv6 address")
.value_parser(parse_ipv6),
),
),
)
.get_matches();

if let Some(matches) = matches.subcommand_matches("common-networking") {
if let Some(matches) = matches.subcommand_matches(COMMON_NW_CMD) {
common_nw_set_up(matches, log.clone()).await?;
}

if let Some(matches) = matches.subcommand_matches("opte-interface") {
if let Some(matches) = matches.subcommand_matches(OPTE_INTERFACE_CMD) {
opte_interface_set_up(matches, log.clone()).await?;
}

if let Some(matches) = matches.subcommand_matches(NTP_CMD) {
ntp_smf_methods(matches, log.clone()).await?;
}

Ok(())
}

async fn ntp_smf_methods(
matches: &ArgMatches,
log: Logger,
) -> Result<(), CmdError> {
if let Some(matches) = matches.subcommand_matches(NTP_START_CMD) {
ntp_smf_start(matches, log.clone()).await?;
}

// TODO: Add refresh and stop
Ok(())
}

async fn ntp_smf_start(
matches: &ArgMatches,
log: Logger,
) -> Result<(), CmdError> {
let servers =
matches.get_many::<String>("servers").unwrap().collect::<Vec<_>>();
let allow: Option<&Ipv6Addr> = matches.get_one("allow");

let file: &String = matches.get_one("file").unwrap();
let is_boundary: &bool = matches.get_one("boundary").unwrap();
println!(
"servers: {:?}\nfile: {}\nallow: {:?}\nboundary: {:?}",
servers, file, allow, is_boundary
);

let template = if *is_boundary {
BOUNDARY_NTP_CONFIG_TPL
} else {
INTERNAL_NTP_CONFIG_TPL
};
info!(&log, "Generating chrony configuration file"; "configuration file" => ?file, "configuration template" => ?template, "allowed IPs" => ?allow, "servers" => ?servers, "is boundary" => ?is_boundary);

// Generate config file
let mut contents = fs::read_to_string(template).map_err(|err| {
CmdError::Failure(anyhow!(
"Could not read chrony configuration template {}: {}",
template,
err
))
})?;
let new_config = if *is_boundary {
let mut contents = contents.replace(
"@ALLOW@",
&allow.expect("Chrony allowed address not supplied").to_string(),
);
for s in servers {
let str_line =
format!("pool {} iburst maxdelay 0.1 maxsources 16\n", s);
contents.push_str(&str_line)
}
contents
} else {
for s in servers {
let str_line = format!("server {} iburst minpoll 0 maxpoll 4\n", s);
contents.push_str(&str_line)
}
contents
};

let mut config_file =
OpenOptions::new().write(true).truncate(true).open(file).map_err(
|err| {
CmdError::Failure(anyhow!(
"Could not create chrony configuration file {}: {}",
file,
err
))
},
)?;
config_file.write(new_config.as_bytes()).map_err(|err| {
CmdError::Failure(anyhow!(
"Could not write to chrony configuration file {}: {}",
file,
err
))
})?;

// TODO: Check if file has changed

// TODO: Update logadm

// TODO: Start daemon
Ok(())
}

Expand Down

0 comments on commit 44e5a91

Please sign in to comment.