1use std::collections::HashMap;
4
5use serde_json::{Map, Value};
6
7use super::{
8 db::parse_str_map, OvsDbConnection, OvsDbMethodTransact, OvsDbMutate,
9 OvsDbMutation, OvsDbOperation, OvsDbSelect, OvsDbUpdate, OVS_DB_NAME,
10};
11use crate::{ErrorKind, MergedNetworkState, NmstateError, OvsDbGlobalConfig};
12
13const GLOBAL_CONFIG_TABLE: &str = "Open_vSwitch";
14
15impl From<&Map<std::string::String, Value>> for OvsDbGlobalConfig {
16 fn from(m: &Map<std::string::String, Value>) -> Self {
17 let mut ret = Self::default();
18 if let (Some(Value::Array(ids)), Some(Value::Array(other_cfg))) =
19 (m.get("external_ids"), m.get("other_config"))
20 {
21 ret.external_ids = Some(convert_map(parse_str_map(ids)));
22 ret.other_config = Some(convert_map(parse_str_map(other_cfg)));
23 }
24 ret
25 }
26}
27
28fn convert_map(
30 mut m: HashMap<String, String>,
31) -> HashMap<String, Option<String>> {
32 let mut ret = HashMap::new();
33 for (k, v) in m.drain() {
34 ret.insert(k, Some(v));
35 }
36 ret
37}
38
39impl OvsDbConnection {
40 pub(crate) async fn get_global_conf(
41 &mut self,
42 ) -> Result<OvsDbGlobalConfig, NmstateError> {
43 let reply = self
44 .transact(&OvsDbMethodTransact {
45 db_name: OVS_DB_NAME.to_string(),
46 operations: vec![OvsDbOperation::Select(OvsDbSelect {
47 table: GLOBAL_CONFIG_TABLE.to_string(),
48 conditions: vec![],
49 columns: Some(vec!["external_ids", "other_config"]),
50 })],
51 })
52 .await?;
53
54 if let Some(global_conf) = reply
55 .as_array()
56 .and_then(|reply| reply.first())
57 .and_then(|v| v.as_object())
58 .and_then(|v| v.get("rows"))
59 .and_then(|v| v.as_array())
60 .and_then(|v| v.first())
61 .and_then(|v| v.as_object())
62 {
63 Ok(global_conf.into())
64 } else {
65 let e = NmstateError::new(
66 ErrorKind::PluginFailure,
67 format!(
68 "Invalid reply from OVSDB for querying \
69 {GLOBAL_CONFIG_TABLE} table: {reply:?}"
70 ),
71 );
72 log::error!("{e}");
73 Err(e)
74 }
75 }
76}
77
78fn append_purge_action(operations: &mut Vec<OvsDbOperation>, column: &str) {
79 log::info!("Purging OVS table {GLOBAL_CONFIG_TABLE} table column {column}");
80 let mut row = HashMap::new();
81 row.insert(
82 column.to_string(),
83 Value::Array(vec![
84 Value::String("map".to_string()),
85 Value::Array(Vec::new()),
86 ]),
87 );
88 operations.push(OvsDbOperation::Update(OvsDbUpdate {
89 table: GLOBAL_CONFIG_TABLE.to_string(),
90 conditions: Vec::new(),
91 row,
92 }))
93}
94
95fn append_mutations(
97 mutations: &mut Vec<OvsDbMutation>,
98 column: &str,
99 data: &HashMap<String, Option<String>>,
100) {
101 let delete_keys: Vec<Value> = data
104 .keys()
105 .map(|k| {
106 log::debug!("Removing old value of key {k} from {column}");
107 Value::String(k.to_string())
108 })
109 .collect();
110 if !delete_keys.is_empty() {
111 mutations.push(OvsDbMutation {
112 column: column.to_string(),
113 mutator: "delete".to_string(),
114 value: Value::Array(vec![
115 Value::String("set".to_string()),
116 Value::Array(delete_keys),
117 ]),
118 })
119 }
120 let insert_values: Vec<Value> = data
121 .iter()
122 .filter_map(|(k, v)| {
123 v.as_ref().map(|v| {
124 log::info!("Inserting key {k} value {v} into {column}");
125 Value::Array(vec![
126 Value::String(k.to_string()),
127 Value::String(v.to_string()),
128 ])
129 })
130 })
131 .collect();
132 if !insert_values.is_empty() {
133 mutations.push(OvsDbMutation {
134 column: column.to_string(),
135 mutator: "insert".to_string(),
136 value: Value::Array(vec![
137 Value::String("map".to_string()),
138 Value::Array(insert_values),
139 ]),
140 })
141 }
142}
143
144pub(crate) async fn ovsdb_apply_global_conf(
145 merged_state: &MergedNetworkState,
146) -> Result<(), NmstateError> {
147 if !merged_state.ovsdb.is_changed() && !merged_state.ovn.is_changed() {
148 log::debug!("No OVSDB changes");
149 return Ok(());
150 }
151
152 let mut cli = OvsDbConnection::new().await?;
153 let mut operations = Vec::new();
154 let mut is_external_ids_purged = false;
155
156 if let Some(desired_ovsdb) = merged_state.ovsdb.desired.as_ref() {
157 if desired_ovsdb.is_purge() {
158 is_external_ids_purged = true;
159 append_purge_action(&mut operations, "external_ids");
160 append_purge_action(&mut operations, "other_config");
161 } else {
162 let mut mutations = Vec::new();
163 if let Some(external_ids) = desired_ovsdb.external_ids.as_ref() {
164 if external_ids.is_empty() {
166 is_external_ids_purged = true;
167 append_purge_action(&mut operations, "external_ids");
168 } else {
169 append_mutations(
170 &mut mutations,
171 "external_ids",
172 external_ids,
173 );
174 }
175 }
176 if let Some(other_config) = desired_ovsdb.other_config.as_ref() {
177 if other_config.is_empty() {
178 append_purge_action(&mut operations, "other_config");
179 } else {
180 append_mutations(
181 &mut mutations,
182 "other_config",
183 other_config,
184 );
185 }
186 }
187 if !mutations.is_empty() {
188 operations.push(OvsDbOperation::Mutate(OvsDbMutate {
189 table: GLOBAL_CONFIG_TABLE.to_string(),
190 conditions: Vec::new(),
191 mutations,
192 }));
193 }
194 }
195 }
196
197 let ovn_map_value = merged_state.ovn.to_ovsdb_external_id_value();
198
199 if merged_state.ovn.is_changed()
202 || (is_external_ids_purged && ovn_map_value.is_some())
203 {
204 let mut ovn_external_ids = HashMap::new();
205 ovn_external_ids
206 .insert("ovn-bridge-mappings".to_string(), ovn_map_value);
207
208 let mut mutations = Vec::new();
209 mutations.push(OvsDbMutation {
211 column: "external_ids".to_string(),
212 mutator: "delete".to_string(),
213 value: Value::Array(vec![
214 Value::String("set".to_string()),
215 Value::Array(vec![Value::String(
216 "ovn-bridge-mappings".to_string(),
217 )]),
218 ]),
219 });
220 append_mutations(&mut mutations, "external_ids", &ovn_external_ids);
221
222 operations.push(OvsDbOperation::Mutate(OvsDbMutate {
223 table: GLOBAL_CONFIG_TABLE.to_string(),
224 conditions: Vec::new(),
225 mutations,
226 }));
227 }
228
229 if !operations.is_empty() {
230 let transact = OvsDbMethodTransact {
231 db_name: OVS_DB_NAME.to_string(),
232 operations,
233 };
234 cli.transact(&transact).await?;
235 }
236 Ok(())
237}