use std::convert::TryFrom;
use std::time::{Duration, Instant};
use log::debug;
use super::{
active_connection::{
get_nm_ac_by_obj_path, nm_ac_obj_path_uuid_get, NmActiveConnection,
},
connection::{nm_con_get_from_obj_path, NmConnection},
dbus::NmDbus,
device::{NmDevice, NmDeviceState, NmDeviceStateReason},
dns::{NmDnsEntry, NmGlobalDnsConfig},
error::{ErrorKind, NmError},
lldp::NmLldpNeighbor,
query_apply::device::{
nm_dev_delete, nm_dev_from_obj_path, nm_dev_get_llpd,
},
};
pub struct NmApi<'a> {
pub(crate) dbus: NmDbus<'a>,
checkpoint: Option<String>,
cp_refresh_time: Option<std::time::Instant>,
cp_timeout: u32,
auto_cp_refresh: bool,
}
impl<'a> NmApi<'a> {
pub fn new() -> Result<Self, NmError> {
Ok(Self {
dbus: NmDbus::new()?,
checkpoint: None,
cp_refresh_time: None,
cp_timeout: 0,
auto_cp_refresh: false,
})
}
pub fn set_checkpoint(&mut self, checkpoint: &str, timeout: u32) {
self.checkpoint = Some(checkpoint.to_string());
self.cp_timeout = timeout;
self.cp_refresh_time = Some(std::time::Instant::now());
}
pub fn set_checkpoint_auto_refresh(&mut self, value: bool) {
self.auto_cp_refresh = value;
}
pub fn version(&self) -> Result<String, NmError> {
self.dbus.version()
}
pub fn checkpoint_create(
&mut self,
timeout: u32,
) -> Result<String, NmError> {
debug!("checkpoint_create");
let cp = self.dbus.checkpoint_create(timeout)?;
debug!("checkpoint created: {}", &cp);
self.checkpoint = Some(cp.clone());
self.cp_refresh_time = Some(std::time::Instant::now());
self.cp_timeout = timeout;
Ok(cp)
}
pub fn checkpoint_destroy(
&mut self,
checkpoint: &str,
) -> Result<(), NmError> {
let mut checkpoint_to_destroy: String = checkpoint.to_string();
if checkpoint_to_destroy.is_empty() {
checkpoint_to_destroy = self.last_active_checkpoint()?
}
self.checkpoint = None;
self.cp_refresh_time = None;
debug!("checkpoint_destroy: {}", checkpoint_to_destroy);
self.dbus.checkpoint_destroy(checkpoint_to_destroy.as_str())
}
pub fn checkpoint_rollback(
&mut self,
checkpoint: &str,
) -> Result<(), NmError> {
let mut checkpoint_to_rollback: String = checkpoint.to_string();
if checkpoint_to_rollback.is_empty() {
checkpoint_to_rollback = self.last_active_checkpoint()?
}
self.checkpoint = None;
self.cp_refresh_time = None;
debug!("checkpoint_rollback: {}", checkpoint_to_rollback);
self.dbus
.checkpoint_rollback(checkpoint_to_rollback.as_str())
}
fn last_active_checkpoint(&self) -> Result<String, NmError> {
debug!("last_active_checkpoint");
let mut checkpoints = self.dbus.checkpoints()?;
if !checkpoints.is_empty() {
Ok(checkpoints.remove(0))
} else {
Err(NmError::new(
ErrorKind::NotFound,
"Not active checkpoints".to_string(),
))
}
}
pub fn connection_activate(&mut self, uuid: &str) -> Result<(), NmError> {
debug!("connection_activate: {}", uuid);
self.extend_timeout_if_required()?;
let nm_conn = self.dbus.get_conn_obj_path_by_uuid(uuid)?;
self.dbus.connection_activate(&nm_conn)
}
pub fn connection_deactivate(&mut self, uuid: &str) -> Result<(), NmError> {
debug!("connection_deactivate: {}", uuid);
self.extend_timeout_if_required()?;
if let Ok(nm_ac) = get_nm_ac_obj_path_by_uuid(&self.dbus, uuid) {
if !nm_ac.is_empty() {
self.dbus.connection_deactivate(&nm_ac)?;
}
}
Ok(())
}
pub fn connections_get(&mut self) -> Result<Vec<NmConnection>, NmError> {
debug!("connections_get");
self.extend_timeout_if_required()?;
let mut nm_conns = Vec::new();
for nm_conn_obj_path in self.dbus.nm_conn_obj_paths_get()? {
if let Ok(c) = nm_con_get_from_obj_path(
&self.dbus.connection,
&nm_conn_obj_path,
) {
nm_conns.push(c);
}
}
Ok(nm_conns)
}
pub fn applied_connections_get(
&mut self,
) -> Result<Vec<NmConnection>, NmError> {
debug!("applied_connections_get");
self.extend_timeout_if_required()?;
let nm_dev_obj_paths = self.dbus.nm_dev_obj_paths_get()?;
let mut nm_conns: Vec<NmConnection> = Vec::new();
for nm_dev_obj_path in nm_dev_obj_paths {
self.extend_timeout_if_required()?;
match self.dbus.nm_dev_applied_connection_get(&nm_dev_obj_path) {
Ok(mut nm_conn) => {
if nm_conn
.connection
.as_ref()
.map(|c| c.iface_name.is_none())
.unwrap_or_default()
{
if let (Ok(nm_dev), Some(nm_set)) = (
nm_dev_from_obj_path(
&self.dbus.connection,
&nm_dev_obj_path,
),
nm_conn.connection.as_mut(),
) {
nm_set.iface_name = Some(nm_dev.name.clone());
}
}
nm_conns.push(nm_conn)
}
Err(e) => {
debug!(
"Ignoring error when get applied connection for \
dev {}: {}",
nm_dev_obj_path, e
);
}
}
}
Ok(nm_conns)
}
pub fn connection_add(
&mut self,
nm_conn: &NmConnection,
memory_only: bool,
) -> Result<(), NmError> {
debug!("connection_add: {:?}", nm_conn);
self.extend_timeout_if_required()?;
if !nm_conn.obj_path.is_empty() {
self.dbus.connection_update(
nm_conn.obj_path.as_str(),
nm_conn,
memory_only,
)
} else {
self.dbus.connection_add(nm_conn, memory_only)
}
}
pub fn connection_delete(&mut self, uuid: &str) -> Result<(), NmError> {
debug!("connection_delete: {}", uuid);
self.extend_timeout_if_required()?;
if let Ok(con_obj_path) = self.dbus.get_conn_obj_path_by_uuid(uuid) {
debug!("Found nm_connection {} for UUID {}", con_obj_path, uuid);
if !con_obj_path.is_empty() {
self.dbus.connection_delete(&con_obj_path)?;
}
}
Ok(())
}
pub fn connection_reapply(
&mut self,
nm_conn: &NmConnection,
) -> Result<(), NmError> {
debug!("connection_reapply: {:?}", nm_conn);
self.extend_timeout_if_required()?;
if let Some(iface_name) = nm_conn.iface_name() {
let nm_dev_obj_path = self.dbus.nm_dev_obj_path_get(iface_name)?;
self.dbus.nm_dev_reapply(&nm_dev_obj_path, nm_conn)
} else {
Err(NmError::new(
ErrorKind::InvalidArgument,
format!(
"Failed to extract interface name from connection {nm_conn:?}"
),
))
}
}
pub fn active_connections_get(
&mut self,
) -> Result<Vec<NmActiveConnection>, NmError> {
debug!("active_connections_get");
self.extend_timeout_if_required()?;
let mut nm_acs = Vec::new();
let nm_ac_obj_paths = self.dbus.active_connections()?;
for nm_ac_obj_path in nm_ac_obj_paths {
if let Ok(Some(nm_ac)) =
get_nm_ac_by_obj_path(&self.dbus.connection, &nm_ac_obj_path)
{
debug!("Got active connection {:?}", nm_ac);
nm_acs.push(nm_ac);
}
}
Ok(nm_acs)
}
pub fn checkpoint_timeout_extend(
&self,
checkpoint: &str,
added_time_sec: u32,
) -> Result<(), NmError> {
debug!(
"checkpoint_timeout_extend: {} {}",
checkpoint, added_time_sec
);
self.dbus
.checkpoint_timeout_extend(checkpoint, added_time_sec)
}
pub fn devices_get(&mut self) -> Result<Vec<NmDevice>, NmError> {
debug!("devices_get");
self.extend_timeout_if_required()?;
let mut ret = Vec::new();
for nm_dev_obj_path in &self.dbus.nm_dev_obj_paths_get()? {
match nm_dev_from_obj_path(&self.dbus.connection, nm_dev_obj_path) {
Ok(nm_dev) => {
debug!("Got Device {:?}", nm_dev);
ret.push(nm_dev);
}
Err(e) => {
debug!(
"Failed to retrieve device {} {}",
nm_dev_obj_path, e
)
}
}
}
Ok(ret)
}
pub fn device_delete(
&mut self,
nm_dev_obj_path: &str,
) -> Result<(), NmError> {
self.extend_timeout_if_required()?;
nm_dev_delete(&self.dbus.connection, nm_dev_obj_path)
}
pub fn device_lldp_neighbor_get(
&mut self,
nm_dev_obj_path: &str,
) -> Result<Vec<NmLldpNeighbor>, NmError> {
self.extend_timeout_if_required()?;
nm_dev_get_llpd(&self.dbus.connection, nm_dev_obj_path)
}
pub fn wait_checkpoint_rollback(
&mut self,
timeout: u32,
) -> Result<(), NmError> {
debug!("wait_checkpoint_rollback");
let start = Instant::now();
while start.elapsed() <= Duration::from_secs(timeout.into()) {
let mut waiting_nm_dev: Vec<&NmDevice> = Vec::new();
let nm_devs = self.devices_get()?;
for nm_dev in &nm_devs {
if nm_dev.state_reason == NmDeviceStateReason::NewActivation
|| nm_dev.state == NmDeviceState::Deactivating
{
waiting_nm_dev.push(nm_dev);
}
}
if waiting_nm_dev.is_empty() {
return Ok(());
} else {
debug!(
"Waiting rollback on these devices {:?}",
waiting_nm_dev
);
std::thread::sleep(Duration::from_millis(500));
}
}
Err(NmError::new(
ErrorKind::Timeout,
"Timeout on waiting rollback".to_string(),
))
}
pub fn get_dns_configuration(
&mut self,
) -> Result<Vec<NmDnsEntry>, NmError> {
let mut ret: Vec<NmDnsEntry> = Vec::new();
self.extend_timeout_if_required()?;
for dns_value in self.dbus.get_dns_configuration()? {
ret.push(NmDnsEntry::try_from(dns_value)?);
}
Ok(ret)
}
pub fn hostname_set(&mut self, hostname: &str) -> Result<(), NmError> {
self.extend_timeout_if_required()?;
if hostname.is_empty() {
if std::path::Path::new("/etc/hostname").exists() {
if let Err(e) = std::fs::remove_file("/etc/hostname") {
log::error!("Failed to remove static /etc/hostname: {}", e);
}
}
Ok(())
} else {
self.dbus.hostname_set(hostname)
}
}
pub fn extend_timeout_if_required(&mut self) -> Result<(), NmError> {
if let (Some(cp_refresh_time), Some(checkpoint)) =
(self.cp_refresh_time.as_ref(), self.checkpoint.as_ref())
{
if self.auto_cp_refresh
&& cp_refresh_time.elapsed().as_secs()
>= self.cp_timeout as u64 / 2
{
log::debug!("Extending checkpoint timeout");
self.checkpoint_timeout_extend(checkpoint, self.cp_timeout)?;
self.cp_refresh_time = Some(Instant::now());
}
}
Ok(())
}
pub fn get_global_dns_configuration(
&self,
) -> Result<NmGlobalDnsConfig, NmError> {
NmGlobalDnsConfig::try_from(self.dbus.global_dns_configuration()?)
}
pub fn set_global_dns_configuration(
&mut self,
config: &NmGlobalDnsConfig,
) -> Result<(), NmError> {
self.extend_timeout_if_required()?;
self.dbus.set_global_dns_configuration(config.to_value()?)
}
}
fn get_nm_ac_obj_path_by_uuid(
dbus: &NmDbus,
uuid: &str,
) -> Result<String, NmError> {
let nm_ac_obj_paths = dbus.active_connections()?;
for nm_ac_obj_path in nm_ac_obj_paths {
if nm_ac_obj_path_uuid_get(&dbus.connection, &nm_ac_obj_path)? == uuid {
return Ok(nm_ac_obj_path);
}
}
Ok("".into())
}