From 2eefd9aee1140f46af875031228e75885e242cb2 Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Fri, 24 Apr 2020 09:55:46 +0200 Subject: [PATCH] src/config/network.rs: implement network reload, set "changes" attribute --- src/api2/config/network.rs | 25 ++++++++++++- src/bin/proxmox-backup-manager.rs | 20 ++++++++--- src/config/network.rs | 9 +++++ src/config/network/helper.rs | 58 +++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/api2/config/network.rs b/src/api2/config/network.rs index 1931196e..3ff4b1e6 100644 --- a/src/api2/config/network.rs +++ b/src/api2/config/network.rs @@ -27,7 +27,7 @@ use crate::api2::types::*; pub fn list_network_devices( _param: Value, _info: &ApiMethod, - _rpcenv: &mut dyn RpcEnvironment, + rpcenv: &mut dyn RpcEnvironment, ) -> Result { let (config, digest) = network::config()?; @@ -41,6 +41,11 @@ pub fn list_network_devices( list.push(item); } + let diff = network::changes()?; + if !diff.is_empty() { + rpcenv.set_result_attrib("changes", diff.into()); + } + Ok(list.into()) } @@ -312,6 +317,23 @@ pub fn delete_interface(name: String, digest: Option) -> Result<(), Erro 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() .get(&API_METHOD_READ_INTERFACE) .put(&API_METHOD_UPDATE_INTERFACE) @@ -319,4 +341,5 @@ const ITEM_ROUTER: Router = Router::new() pub const ROUTER: Router = Router::new() .get(&API_METHOD_LIST_NETWORK_DEVICES) + .put(&API_METHOD_RELOAD_NETWORK_CONFIG) .match_all("name", &ITEM_ROUTER); diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index ff29fe7b..0ff4d4a0 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -254,6 +254,12 @@ fn list_network_devices(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result _ => 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 { let mut text = String::new(); @@ -300,15 +306,21 @@ fn network_commands() -> CommandLineInterface { let cmd_def = CliCommandMap::new() .insert("list", CliCommand::new(&API_METHOD_LIST_NETWORK_DEVICES)) - .insert("update", - CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE) + .insert( + "update", + CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE) .arg_param(&["name"]) .completion_cb("name", config::network::complete_interface_name) ) - .insert("remove", - CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE) + .insert( + "remove", + CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE) .arg_param(&["name"]) .completion_cb("name", config::network::complete_interface_name) + ) + .insert( + "reload", + CliCommand::new(&api2::config::network::API_METHOD_RELOAD_NETWORK_CONFIG) ); cmd_def.into() diff --git a/src/config/network.rs b/src/config/network.rs index 5b263115..1b7d02cc 100644 --- a/src/config/network.rs +++ b/src/config/network.rs @@ -387,6 +387,15 @@ pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> { Ok((data, digest)) } +pub fn changes() -> Result { + + 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> { let mut raw = Vec::new(); diff --git a/src/config/network/helper.rs b/src/config/network/helper.rs index 5aa5d362..569594d8 100644 --- a/src/config/network/helper.rs +++ b/src/config/network/helper.rs @@ -1,3 +1,5 @@ +use std::path::Path; +use std::process::Command; use std::collections::HashMap; use anyhow::{Error, bail, format_err}; @@ -136,3 +138,59 @@ pub fn get_network_interfaces() -> Result, Error> { Ok(interface_list) } + +pub fn compute_file_diff(filename: &str, shadow: &str) -> Result { + + 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(()) +}