nmstate/ovsdb/
global_conf.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use 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
28// Convert HashMap<String, String> to HashMap<String, Option<String>>
29fn 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
95// Use `delete` mutator for Value set to None, otherwise use `insert`.
96fn append_mutations(
97    mutations: &mut Vec<OvsDbMutation>,
98    column: &str,
99    data: &HashMap<String, Option<String>>,
100) {
101    // All key should be deleted first because `insert` does not override
102    // existing values.
103    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                // Whether user is purging all external_ids
165                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    // When OVSDB is purging, we should preserve current OVN mapping
200    // regardless it is changed or not.
201    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        // Remove then insert again
210        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}