Blame SOURCES/0001-npc-Show-brief-network-state-when-no-argument.patch

8f0e8e
From 69cc9aaf259d4c55b74d7b75037992431136ba42 Mon Sep 17 00:00:00 2001
8f0e8e
From: Gris Ge <fge@redhat.com>
8f0e8e
Date: Fri, 28 May 2021 18:18:13 +0800
8f0e8e
Subject: [PATCH 1/3] npc: Show brief network state when no argument
8f0e8e
8f0e8e
When running `npc` without any argument, instead of dumping all
8f0e8e
information, this patch changed to show brief network status.
8f0e8e
8f0e8e
Added new sub command `iface` to `npc` for old behaviour:
8f0e8e
8f0e8e
    * `npc eth1` -- show eth1 interface info.
8f0e8e
    * `npc iface eth1` -- the same as `npc eth1`.
8f0e8e
    * `npc iface` -- show full information in yaml format.
8f0e8e
    * `npc` -- show brief network state.
8f0e8e
    * `npc --json` -- show brief network state in json format.
8f0e8e
8f0e8e
Example on running `npc` without argument:
8f0e8e
8f0e8e
```
8f0e8e
1: lo: <LOOPBACK,LOWERUP,RUNNING,UP> state unknown mtu 65536
8f0e8e
    mac 00:00:00:00:00:00
8f0e8e
    ipv4 127.0.0.1/8
8f0e8e
    ipv6 ::1/128
8f0e8e
2: eth1: <BROADCAST,LOWERUP,MULTICAST,RUNNING,UP> state up mtu 1500
8f0e8e
    mac 00:01:02:03:04:05
8f0e8e
    ipv4 192.0.2.6/24 gw4 192.0.2.1
8f0e8e
    ipv6 2001:db8::6/64 fe80::6/64 gw6 fe80::1
8f0e8e
```
8f0e8e
8f0e8e
This patch contains many memory copy done by `to_string()`, `clone()`,
8f0e8e
`to_vec()`. We can improve it in future if that cause any problem.
8f0e8e
8f0e8e
Signed-off-by: Gris Ge <fge@redhat.com>
8f0e8e
---
8f0e8e
 src/cli/npc.rs          | 232 +++++++++++++++++++++++++++++++++++++---
8f0e8e
 src/lib/ifaces/iface.rs |  17 +++
8f0e8e
 2 files changed, 237 insertions(+), 12 deletions(-)
8f0e8e
8f0e8e
diff --git a/src/cli/npc.rs b/src/cli/npc.rs
8f0e8e
index b7171bb..41ddbd8 100644
8f0e8e
--- a/src/cli/npc.rs
8f0e8e
+++ b/src/cli/npc.rs
8f0e8e
@@ -13,19 +13,188 @@
8f0e8e
 // limitations under the License.
8f0e8e
 
8f0e8e
 use clap::{clap_app, crate_authors, crate_version};
8f0e8e
-use nispor::{Iface, NetConf, NetState, NisporError, Route, RouteRule};
8f0e8e
+use nispor::{
8f0e8e
+    Iface, IfaceState, NetConf, NetState, NisporError, Route, RouteRule,
8f0e8e
+};
8f0e8e
 use serde_derive::Serialize;
8f0e8e
 use serde_json;
8f0e8e
 use serde_yaml;
8f0e8e
+use std::collections::HashMap;
8f0e8e
 use std::fmt;
8f0e8e
 use std::io::{stderr, stdout, Write};
8f0e8e
 use std::process;
8f0e8e
 
8f0e8e
-#[derive(Serialize)]
8f0e8e
+const INDENT: &str = "    ";
8f0e8e
+
8f0e8e
+#[derive(Serialize, Debug)]
8f0e8e
 pub struct CliError {
8f0e8e
     pub msg: String,
8f0e8e
 }
8f0e8e
 
8f0e8e
+#[derive(Serialize, Default)]
8f0e8e
+struct CliIfaceBrief {
8f0e8e
+    index: u32,
8f0e8e
+    name: String,
8f0e8e
+    state: IfaceState,
8f0e8e
+    flags: Vec<String>,
8f0e8e
+    mac: String,
8f0e8e
+    permanent_mac: String,
8f0e8e
+    mtu: i64,
8f0e8e
+    ipv4: Vec<String>,
8f0e8e
+    ipv6: Vec<String>,
8f0e8e
+    gw4: Vec<String>,
8f0e8e
+    gw6: Vec<String>,
8f0e8e
+}
8f0e8e
+
8f0e8e
+impl CliIfaceBrief {
8f0e8e
+    fn to_string(briefs: &[CliIfaceBrief]) -> String {
8f0e8e
+        let mut ret = Vec::new();
8f0e8e
+        for brief in briefs {
8f0e8e
+            ret.push(format!(
8f0e8e
+                "{}: {}: <{}> state {} mtu {}",
8f0e8e
+                brief.index,
8f0e8e
+                brief.name,
8f0e8e
+                brief.flags.join(","),
8f0e8e
+                brief.state,
8f0e8e
+                brief.mtu,
8f0e8e
+            ));
8f0e8e
+            if &brief.mac != "" {
8f0e8e
+                ret.push(format!(
8f0e8e
+                    "{}mac {}{}",
8f0e8e
+                    INDENT,
8f0e8e
+                    brief.mac,
8f0e8e
+                    if &brief.permanent_mac != ""
8f0e8e
+                        && &brief.permanent_mac != &brief.mac
8f0e8e
+                    {
8f0e8e
+                        format!(" permanent_mac: {}", brief.permanent_mac)
8f0e8e
+                    } else {
8f0e8e
+                        "".into()
8f0e8e
+                    }
8f0e8e
+                ));
8f0e8e
+            }
8f0e8e
+            if brief.ipv4.len() > 0 {
8f0e8e
+                ret.push(format!(
8f0e8e
+                    "{}ipv4 {}{}",
8f0e8e
+                    INDENT,
8f0e8e
+                    brief.ipv4.join(" "),
8f0e8e
+                    if brief.gw4.len() > 0 {
8f0e8e
+                        format!(" gw4 {}", brief.gw4.join(" "))
8f0e8e
+                    } else {
8f0e8e
+                        "".into()
8f0e8e
+                    },
8f0e8e
+                ));
8f0e8e
+            }
8f0e8e
+            if brief.ipv6.len() > 0 {
8f0e8e
+                ret.push(format!(
8f0e8e
+                    "{}ipv6 {}{}",
8f0e8e
+                    INDENT,
8f0e8e
+                    brief.ipv6.join(" "),
8f0e8e
+                    if brief.gw6.len() > 0 {
8f0e8e
+                        format!(" gw6 {}", brief.gw6.join(" "))
8f0e8e
+                    } else {
8f0e8e
+                        "".into()
8f0e8e
+                    }
8f0e8e
+                ));
8f0e8e
+            }
8f0e8e
+        }
8f0e8e
+        ret.join("\n")
8f0e8e
+    }
8f0e8e
+
8f0e8e
+    fn from_net_state(netstate: &NetState) -> Vec<Self> {
8f0e8e
+        let mut ret = Vec::new();
8f0e8e
+        let mut iface_to_gw4: HashMap<String, Vec<String>> = HashMap::new();
8f0e8e
+        let mut iface_to_gw6: HashMap<String, Vec<String>> = HashMap::new();
8f0e8e
+
8f0e8e
+        for route in &netstate.routes {
8f0e8e
+            if let Route {
8f0e8e
+                dst: None,
8f0e8e
+                gateway: Some(gw),
8f0e8e
+                oif: Some(iface_name),
8f0e8e
+                ..
8f0e8e
+            } = route
8f0e8e
+            {
8f0e8e
+                if gw.contains(":") {
8f0e8e
+                    match iface_to_gw6.get_mut(iface_name) {
8f0e8e
+                        Some(gateways) => {
8f0e8e
+                            gateways.push(gw.to_string());
8f0e8e
+                        }
8f0e8e
+                        None => {
8f0e8e
+                            iface_to_gw6.insert(
8f0e8e
+                                iface_name.to_string(),
8f0e8e
+                                vec![gw.to_string()],
8f0e8e
+                            );
8f0e8e
+                        }
8f0e8e
+                    }
8f0e8e
+                } else {
8f0e8e
+                    match iface_to_gw4.get_mut(iface_name) {
8f0e8e
+                        Some(gateways) => {
8f0e8e
+                            gateways.push(gw.to_string());
8f0e8e
+                        }
8f0e8e
+                        None => {
8f0e8e
+                            iface_to_gw4.insert(
8f0e8e
+                                iface_name.to_string(),
8f0e8e
+                                vec![gw.to_string()],
8f0e8e
+                            );
8f0e8e
+                        }
8f0e8e
+                    }
8f0e8e
+                }
8f0e8e
+            }
8f0e8e
+        }
8f0e8e
+
8f0e8e
+        for iface in netstate.ifaces.values() {
8f0e8e
+            ret.push(CliIfaceBrief {
8f0e8e
+                index: iface.index,
8f0e8e
+                name: iface.name.clone(),
8f0e8e
+                flags: (&iface.flags)
8f0e8e
+                    .into_iter()
8f0e8e
+                    .map(|flag| format!("{:?}", flag).to_uppercase())
8f0e8e
+                    .collect(),
8f0e8e
+                state: iface.state.clone(),
8f0e8e
+                mac: iface.mac_address.clone(),
8f0e8e
+                permanent_mac: iface.permanent_mac_address.clone(),
8f0e8e
+                mtu: iface.mtu,
8f0e8e
+                ipv4: match &iface.ipv4 {
8f0e8e
+                    Some(ip_info) => {
8f0e8e
+                        let mut addr_strs = Vec::new();
8f0e8e
+                        for addr in &ip_info.addresses {
8f0e8e
+                            addr_strs.push(format!(
8f0e8e
+                                "{}/{}",
8f0e8e
+                                addr.address, addr.prefix_len
8f0e8e
+                            ));
8f0e8e
+                        }
8f0e8e
+                        addr_strs
8f0e8e
+                    }
8f0e8e
+                    None => Vec::new(),
8f0e8e
+                },
8f0e8e
+                ipv6: match &iface.ipv6 {
8f0e8e
+                    Some(ip_info) => {
8f0e8e
+                        let mut addr_strs = Vec::new();
8f0e8e
+                        for addr in &ip_info.addresses {
8f0e8e
+                            addr_strs.push(format!(
8f0e8e
+                                "{}/{}",
8f0e8e
+                                addr.address, addr.prefix_len
8f0e8e
+                            ));
8f0e8e
+                        }
8f0e8e
+                        addr_strs
8f0e8e
+                    }
8f0e8e
+                    None => Vec::new(),
8f0e8e
+                },
8f0e8e
+                gw4: match &iface_to_gw4.get(&iface.name) {
8f0e8e
+                    Some(gws) => gws.to_vec(),
8f0e8e
+                    None => Vec::new(),
8f0e8e
+                },
8f0e8e
+                gw6: match &iface_to_gw6.get(&iface.name) {
8f0e8e
+                    Some(gws) => gws.to_vec(),
8f0e8e
+                    None => Vec::new(),
8f0e8e
+                },
8f0e8e
+                ..Default::default()
8f0e8e
+            })
8f0e8e
+        }
8f0e8e
+        ret.sort_by(|a, b| a.index.cmp(&b.index));
8f0e8e
+        ret
8f0e8e
+    }
8f0e8e
+}
8f0e8e
+
8f0e8e
 impl fmt::Display for CliError {
8f0e8e
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8f0e8e
         write!(f, "{}", self.msg)
8f0e8e
@@ -34,6 +203,7 @@ impl fmt::Display for CliError {
8f0e8e
 
8f0e8e
 enum CliResult {
8f0e8e
     Pass,
8f0e8e
+    Brief(Vec<CliIfaceBrief>),
8f0e8e
     Full(NetState),
8f0e8e
     Ifaces(Vec<Iface>),
8f0e8e
     Routes(Vec<Route>),
8f0e8e
@@ -42,6 +212,7 @@ enum CliResult {
8f0e8e
     NisporError(NisporError),
8f0e8e
 }
8f0e8e
 
8f0e8e
+#[derive(PartialEq)]
8f0e8e
 enum CliOutputType {
8f0e8e
     Json,
8f0e8e
     Yaml,
8f0e8e
@@ -53,6 +224,7 @@ macro_rules! npc_print {
8f0e8e
             CliResult::Pass => {
8f0e8e
                 process::exit(0);
8f0e8e
             }
8f0e8e
+            CliResult::Brief(_) => unreachable!(),
8f0e8e
             CliResult::Full(netstate) => {
8f0e8e
                 writeln!(stdout(), "{}", $display_func(&netstate).unwrap())
8f0e8e
                     .ok();
8f0e8e
@@ -83,9 +255,24 @@ macro_rules! npc_print {
8f0e8e
 }
8f0e8e
 
8f0e8e
 fn print_result(result: &CliResult, output_type: CliOutputType) {
8f0e8e
-    match output_type {
8f0e8e
-        CliOutputType::Json => npc_print!(serde_json::to_string_pretty, result),
8f0e8e
-        CliOutputType::Yaml => npc_print!(serde_yaml::to_string, result),
8f0e8e
+    if let CliResult::Brief(briefs) = result {
8f0e8e
+        if output_type == CliOutputType::Json {
8f0e8e
+            writeln!(
8f0e8e
+                stdout(),
8f0e8e
+                "{}",
8f0e8e
+                serde_json::to_string_pretty(&briefs).unwrap()
8f0e8e
+            )
8f0e8e
+            .ok();
8f0e8e
+        } else {
8f0e8e
+            writeln!(stdout(), "{}", CliIfaceBrief::to_string(&briefs)).ok();
8f0e8e
+        }
8f0e8e
+    } else {
8f0e8e
+        match output_type {
8f0e8e
+            CliOutputType::Json => {
8f0e8e
+                npc_print!(serde_json::to_string_pretty, result)
8f0e8e
+            }
8f0e8e
+            CliOutputType::Yaml => npc_print!(serde_yaml::to_string, result),
8f0e8e
+        }
8f0e8e
     }
8f0e8e
 }
8f0e8e
 
8f0e8e
@@ -133,6 +320,11 @@ fn main() {
8f0e8e
         (about: "Nispor CLI")
8f0e8e
         (@arg ifname: [INTERFACE_NAME] "interface name")
8f0e8e
         (@arg json: -j --json "Show in json format")
8f0e8e
+        (@subcommand iface =>
8f0e8e
+            (@arg json: -j --json "Show in json format")
8f0e8e
+            (@arg ifname: [INTERFACE_NAME] "Show only specified interface")
8f0e8e
+            (about: "Show interface")
8f0e8e
+        )
8f0e8e
         (@subcommand route =>
8f0e8e
             (@arg json: -j --json "Show in json format")
8f0e8e
             (@arg dev: -d --dev [OIF] "Show only route entries with output to the specified interface")
8f0e8e
@@ -162,13 +354,21 @@ fn main() {
8f0e8e
     } else {
8f0e8e
         let result = match NetState::retrieve() {
8f0e8e
             Ok(mut state) => {
8f0e8e
-                if let Some(ifname) = matches.value_of("ifname") {
8f0e8e
-                    if let Some(iface) = state.ifaces.remove(ifname) {
8f0e8e
-                        CliResult::Ifaces(vec![iface])
8f0e8e
+                if let Some(m) = matches.subcommand_matches("iface") {
8f0e8e
+                    output_format = parse_arg_output_format(m);
8f0e8e
+                    if let Some(ifname) = m.value_of("ifname") {
8f0e8e
+                        if let Some(iface) = state.ifaces.remove(ifname) {
8f0e8e
+                            CliResult::Ifaces(vec![iface])
8f0e8e
+                        } else {
8f0e8e
+                            CliResult::CliError(CliError {
8f0e8e
+                                msg: format!(
8f0e8e
+                                    "Interface '{}' not found",
8f0e8e
+                                    ifname
8f0e8e
+                                ),
8f0e8e
+                            })
8f0e8e
+                        }
8f0e8e
                     } else {
8f0e8e
-                        CliResult::CliError(CliError {
8f0e8e
-                            msg: format!("Interface '{}' not found", ifname),
8f0e8e
-                        })
8f0e8e
+                        CliResult::Full(state)
8f0e8e
                     }
8f0e8e
                 } else if let Some(m) = matches.subcommand_matches("route") {
8f0e8e
                     output_format = parse_arg_output_format(m);
8f0e8e
@@ -176,9 +376,17 @@ fn main() {
8f0e8e
                 } else if let Some(m) = matches.subcommand_matches("rule") {
8f0e8e
                     output_format = parse_arg_output_format(m);
8f0e8e
                     CliResult::RouteRules(state.rules)
8f0e8e
+                } else if let Some(ifname) = matches.value_of("ifname") {
8f0e8e
+                    if let Some(iface) = state.ifaces.remove(ifname) {
8f0e8e
+                        CliResult::Ifaces(vec![iface])
8f0e8e
+                    } else {
8f0e8e
+                        CliResult::CliError(CliError {
8f0e8e
+                            msg: format!("Interface '{}' not found", ifname),
8f0e8e
+                        })
8f0e8e
+                    }
8f0e8e
                 } else {
8f0e8e
                     /* Show everything if no cmdline arg has been supplied */
8f0e8e
-                    CliResult::Full(state)
8f0e8e
+                    CliResult::Brief(CliIfaceBrief::from_net_state(&state))
8f0e8e
                 }
8f0e8e
             }
8f0e8e
             Err(e) => CliResult::NisporError(e),
8f0e8e
diff --git a/src/lib/ifaces/iface.rs b/src/lib/ifaces/iface.rs
8f0e8e
index ee71b0f..60afacd 100644
8f0e8e
--- a/src/lib/ifaces/iface.rs
8f0e8e
+++ b/src/lib/ifaces/iface.rs
8f0e8e
@@ -102,6 +102,23 @@ impl Default for IfaceState {
8f0e8e
     }
8f0e8e
 }
8f0e8e
 
8f0e8e
+impl std::fmt::Display for IfaceState {
8f0e8e
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8f0e8e
+        write!(
8f0e8e
+            f,
8f0e8e
+            "{}",
8f0e8e
+            match self {
8f0e8e
+                Self::Up => "up",
8f0e8e
+                Self::Dormant => "dormant",
8f0e8e
+                Self::Down => "down",
8f0e8e
+                Self::LowerLayerDown => "lower_layer_down",
8f0e8e
+                Self::Other(s) => s.as_str(),
8f0e8e
+                Self::Unknown => "unknown",
8f0e8e
+            }
8f0e8e
+        )
8f0e8e
+    }
8f0e8e
+}
8f0e8e
+
8f0e8e
 #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
8f0e8e
 #[serde(rename_all = "snake_case")]
8f0e8e
 pub enum IfaceFlags {
8f0e8e
-- 
8f0e8e
2.32.0
8f0e8e