use std::collections::HashMap;
use serde_json::Value;
use super::{
OvsDbMethodEcho, OvsDbMethodTransact, OvsDbOperation, OvsDbSelect,
json_rpc::OvsDbJsonRpc,
};
use crate::{ErrorKind, NmstateError};
pub(crate) const OVS_DB_NAME: &str = "Open_vSwitch";
const NM_RESERVED_EXTERNAL_ID: &str = "NM.connection.uuid";
pub(crate) const DEFAULT_OVS_DB_SOCKET_PATH: &str = "/run/openvswitch/db.sock";
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub(crate) struct OvsDbCondition {
column: String,
function: String,
value: Value,
}
impl OvsDbCondition {
pub(crate) fn to_value(&self) -> Value {
Value::Array(vec![
Value::String(self.column.to_string()),
Value::String(self.function.to_string()),
self.value.clone(),
])
}
}
#[derive(Debug)]
pub(crate) struct OvsDbConnection {
pub(crate) rpc: OvsDbJsonRpc,
pub(crate) transaction_id: u64,
}
impl OvsDbConnection {
pub(crate) fn get_transaction_id(&mut self) -> u64 {
self.transaction_id += 1;
self.transaction_id
}
pub(crate) async fn new() -> Result<Self, NmstateError> {
Ok(Self {
rpc: OvsDbJsonRpc::connect(DEFAULT_OVS_DB_SOCKET_PATH).await?,
transaction_id: 0,
})
}
pub(crate) async fn check_connection(&mut self) -> bool {
let transaction_id = self.get_transaction_id();
let value = OvsDbMethodEcho::to_value(transaction_id);
if self.rpc.send(&value).await.is_ok() {
self.rpc.recv(transaction_id).await.is_ok()
} else {
false
}
}
pub(crate) async fn transact(
&mut self,
transact: &OvsDbMethodTransact,
) -> Result<Value, NmstateError> {
let transaction_id = self.get_transaction_id();
let value = transact.to_value(transaction_id);
self.rpc.send(&value).await?;
let reply = self.rpc.recv(transaction_id).await?;
check_transact_error(reply)
}
async fn _get_ovs_entry(
&mut self,
table_name: &str,
columns: Vec<&'static str>,
) -> Result<HashMap<String, OvsDbEntry>, NmstateError> {
let reply = self
.transact(&OvsDbMethodTransact {
db_name: OVS_DB_NAME.to_string(),
operations: vec![OvsDbOperation::Select(OvsDbSelect {
table: table_name.to_string(),
conditions: vec![],
columns: Some(columns),
})],
})
.await?;
let mut ret: HashMap<String, OvsDbEntry> = HashMap::new();
if let Some(entries) = reply
.as_array()
.and_then(|reply| reply.first())
.and_then(|v| v.as_object())
.and_then(|v| v.get("rows"))
.and_then(|v| v.as_array())
{
for entry in entries {
let ovsdb_entry: OvsDbEntry = entry.try_into()?;
if !ovsdb_entry.uuid.is_empty() {
ret.insert(ovsdb_entry.uuid.to_string(), ovsdb_entry);
}
}
Ok(ret)
} else {
let e = NmstateError::new(
ErrorKind::PluginFailure,
format!(
"Invalid reply from OVSDB for querying {table_name} \
table: {reply:?}"
),
);
log::error!("{e}");
Err(e)
}
}
pub(crate) async fn get_ovs_ifaces(
&mut self,
) -> Result<HashMap<String, OvsDbEntry>, NmstateError> {
self._get_ovs_entry(
"Interface",
vec![
"external_ids",
"name",
"other_config",
"_uuid",
"type",
"mtu",
"options",
],
)
.await
}
pub(crate) async fn get_ovs_ports(
&mut self,
) -> Result<HashMap<String, OvsDbEntry>, NmstateError> {
self._get_ovs_entry(
"Port",
vec![
"external_ids",
"name",
"other_config",
"_uuid",
"interfaces",
"vlan_mode",
"tag",
"trunks",
"bond_mode",
"bond_updelay",
"bond_downdelay",
"lacp",
],
)
.await
}
pub(crate) async fn get_ovs_bridges(
&mut self,
) -> Result<HashMap<String, OvsDbEntry>, NmstateError> {
self._get_ovs_entry(
"Bridge",
vec![
"external_ids",
"name",
"other_config",
"_uuid",
"ports",
"stp_enable",
"rstp_enable",
"mcast_snooping_enable",
"fail_mode",
"datapath_type",
],
)
.await
}
}
#[derive(Debug, Default)]
pub(crate) struct OvsDbEntry {
pub(crate) uuid: String,
pub(crate) name: String,
pub(crate) external_ids: HashMap<String, String>,
pub(crate) other_config: HashMap<String, String>,
pub(crate) ports: Vec<String>,
pub(crate) iface_type: String,
pub(crate) options: HashMap<String, Value>,
}
impl TryFrom<&Value> for OvsDbEntry {
type Error = NmstateError;
fn try_from(v: &Value) -> Result<OvsDbEntry, Self::Error> {
let e = NmstateError::new(
ErrorKind::PluginFailure,
format!("Failed to parse OVS Entry info from : {v:?}"),
);
let v = v.clone();
let mut ret = OvsDbEntry::default();
if let Value::Object(mut v) = v
&& let Some(Value::String(n)) = v.remove("name")
{
ret.name = n;
if let Some(Value::Array(uuid)) = v.remove("_uuid")
&& let Some(Value::String(uuid)) = uuid.get(1)
{
ret.uuid = uuid.to_string();
}
if let Some(Value::String(iface_type)) = v.remove("type") {
ret.iface_type = iface_type;
}
if let Some(Value::Array(ids)) = v.remove("external_ids") {
ret.external_ids = parse_str_map(&ids);
}
if let Some(Value::Array(cfgs)) = v.remove("other_config") {
ret.other_config = parse_str_map(&cfgs);
}
if let Some(Value::Array(ports)) = v.remove("ports") {
ret.ports = parse_uuid_array(&ports);
}
if let Some(Value::Array(ports)) = v.remove("interfaces") {
ret.ports = parse_uuid_array(&ports);
}
for (key, value) in v.iter() {
ret.options.insert(key.to_string(), value.clone());
}
return Ok(ret);
}
log::error!("{e}");
Err(e)
}
}
pub(crate) fn parse_str_map(v: &[Value]) -> HashMap<String, String> {
let mut ret = HashMap::new();
if let Some(Value::String(value_type)) = v.first() {
match value_type.as_str() {
"map" => {
if let Some(ids) = v.get(1).and_then(|i| i.as_array()) {
for kv in ids {
if let Some(kv) = kv.as_array()
&& let (
Some(Value::String(k)),
Some(Value::String(v)),
) = (kv.first(), kv.get(1))
{
if k == NM_RESERVED_EXTERNAL_ID {
continue;
}
ret.insert(k.to_string(), v.to_string());
}
}
}
}
t => {
log::warn!("Got unknown value type {t}: {v:?}");
}
}
}
ret
}
pub(crate) fn parse_uuid_array(v: &[Value]) -> Vec<String> {
let mut ret = Vec::new();
if let Some(Value::String(value_type)) = v.first() {
match value_type.as_str() {
"set" => {
if let Some(vs) = v.get(1).and_then(|i| i.as_array()) {
for v in vs {
if let Some(kv) = v.as_array()
&& let (
Some(Value::String(k)),
Some(Value::String(v)),
) = (kv.first(), kv.get(1))
{
if k != "uuid" {
continue;
}
ret.push(v.to_string());
}
}
}
}
"uuid" => {
if let Some(Value::String(v)) = v.get(1) {
ret.push(v.to_string());
}
}
t => {
log::warn!("Got unknown value type {t}: {v:?}");
}
}
}
ret
}
fn check_transact_error(reply: Value) -> Result<Value, NmstateError> {
if let Some(trans_replies) = reply.as_array() {
for trans_reply in trans_replies {
if let Some(error_type) = trans_reply
.as_object()
.and_then(|r| r.get("error"))
.and_then(|e| e.as_str())
{
let error_detail = trans_reply
.as_object()
.and_then(|r| r.get("details"))
.and_then(|d| d.as_str())
.unwrap_or("unknown error");
let e = NmstateError::new(
ErrorKind::PluginFailure,
format!(
"OVS DB JSON RPC error {error_type}: {error_detail}"
),
);
log::error!("{e}");
return Err(e);
}
}
}
Ok(reply)
}