src/config/network.rs: implement network reload, set "changes" attribute

This commit is contained in:
Dietmar Maurer 2020-04-24 09:55:46 +02:00
parent 8a6b86b8a7
commit 2eefd9aee1
4 changed files with 107 additions and 5 deletions

View File

@ -27,7 +27,7 @@ use crate::api2::types::*;
pub fn list_network_devices( pub fn list_network_devices(
_param: Value, _param: Value,
_info: &ApiMethod, _info: &ApiMethod,
_rpcenv: &mut dyn RpcEnvironment, rpcenv: &mut dyn RpcEnvironment,
) -> Result<Value, Error> { ) -> Result<Value, Error> {
let (config, digest) = network::config()?; let (config, digest) = network::config()?;
@ -41,6 +41,11 @@ pub fn list_network_devices(
list.push(item); list.push(item);
} }
let diff = network::changes()?;
if !diff.is_empty() {
rpcenv.set_result_attrib("changes", diff.into());
}
Ok(list.into()) Ok(list.into())
} }
@ -312,6 +317,23 @@ pub fn delete_interface(name: String, digest: Option<String>) -> Result<(), Erro
Ok(()) Ok(())
} }
#[api(
access: {
permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
},
)]
/// Reload network configuration (requires ifupdown2).
pub fn reload_network_config() -> Result<(), Error> {
network::assert_ifupdown2_installed()?;
let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
network::network_reload()?;
Ok(())
}
const ITEM_ROUTER: Router = Router::new() const ITEM_ROUTER: Router = Router::new()
.get(&API_METHOD_READ_INTERFACE) .get(&API_METHOD_READ_INTERFACE)
.put(&API_METHOD_UPDATE_INTERFACE) .put(&API_METHOD_UPDATE_INTERFACE)
@ -319,4 +341,5 @@ const ITEM_ROUTER: Router = Router::new()
pub const ROUTER: Router = Router::new() pub const ROUTER: Router = Router::new()
.get(&API_METHOD_LIST_NETWORK_DEVICES) .get(&API_METHOD_LIST_NETWORK_DEVICES)
.put(&API_METHOD_RELOAD_NETWORK_CONFIG)
.match_all("name", &ITEM_ROUTER); .match_all("name", &ITEM_ROUTER);

View File

@ -254,6 +254,12 @@ fn list_network_devices(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result
_ => unreachable!(), _ => unreachable!(),
}; };
if let Some(changes) = rpcenv.get_result_attrib("changes") {
if let Some(diff) = changes.as_str() {
eprintln!("pending changes:\n{}\n", diff);
}
}
fn render_address(_value: &Value, record: &Value) -> Result<String, Error> { fn render_address(_value: &Value, record: &Value) -> Result<String, Error> {
let mut text = String::new(); let mut text = String::new();
@ -300,15 +306,21 @@ fn network_commands() -> CommandLineInterface {
let cmd_def = CliCommandMap::new() let cmd_def = CliCommandMap::new()
.insert("list", CliCommand::new(&API_METHOD_LIST_NETWORK_DEVICES)) .insert("list", CliCommand::new(&API_METHOD_LIST_NETWORK_DEVICES))
.insert("update", .insert(
"update",
CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE) CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE)
.arg_param(&["name"]) .arg_param(&["name"])
.completion_cb("name", config::network::complete_interface_name) .completion_cb("name", config::network::complete_interface_name)
) )
.insert("remove", .insert(
"remove",
CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE) CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE)
.arg_param(&["name"]) .arg_param(&["name"])
.completion_cb("name", config::network::complete_interface_name) .completion_cb("name", config::network::complete_interface_name)
)
.insert(
"reload",
CliCommand::new(&api2::config::network::API_METHOD_RELOAD_NETWORK_CONFIG)
); );
cmd_def.into() cmd_def.into()

View File

@ -387,6 +387,15 @@ pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> {
Ok((data, digest)) Ok((data, digest))
} }
pub fn changes() -> Result<String, Error> {
if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() {
return Ok(String::new());
}
compute_file_diff(NETWORK_INTERFACES_FILENAME, NETWORK_INTERFACES_NEW_FILENAME)
}
pub fn save_config(config: &NetworkConfig) -> Result<(), Error> { pub fn save_config(config: &NetworkConfig) -> Result<(), Error> {
let mut raw = Vec::new(); let mut raw = Vec::new();

View File

@ -1,3 +1,5 @@
use std::path::Path;
use std::process::Command;
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::{Error, bail, format_err}; use anyhow::{Error, bail, format_err};
@ -136,3 +138,59 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> {
Ok(interface_list) Ok(interface_list)
} }
pub fn compute_file_diff(filename: &str, shadow: &str) -> Result<String, Error> {
let output = Command::new("/usr/bin/diff")
.arg("-b")
.arg("-u")
.arg(filename)
.arg(shadow)
.output()
.map_err(|err| format_err!("failed to execute diff - {}", err))?;
if !output.status.success() {
match output.status.code() {
Some(code) => {
if code == 0 { return Ok(String::new()); }
if code != 1 {
let msg = String::from_utf8(output.stderr)
.map(|m| if m.is_empty() { String::from("no error message") } else { m })
.unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
bail!("diff failed with status code: {} - {}", code, msg);
}
}
None => bail!("diff terminated by signal"),
}
}
let diff = String::from_utf8(output.stdout)?;
Ok(diff)
}
pub fn assert_ifupdown2_installed() -> Result<(), Error> {
if !Path::new("/usr/share/ifupdown2").exists() {
bail!("ifupdown2 is not installed.");
}
Ok(())
}
pub fn network_reload() -> Result<(), Error> {
let status = Command::new("/sbin/ifreload")
.arg("-a")
.status()
.map_err(|err| format_err!("failed to execute ifreload: - {}", err))?;
if !status.success() {
match status.code() {
Some(code) => bail!("ifreload failed with status code: {}", code),
None => bail!("ifreload terminated by signal")
}
}
Ok(())
}