rust: Support showing running config only

Support showing running config only excluding:
 * IP address retrieved by DHCP or IPv6 auto configuration.
 * DNS client resolver retrieved by DHCP or IPv6 auto configuration.
 * Routes retrieved by DHCPv4 or IPv6 router advertisement.
 * LLDP neighbor information.

API:
 * Rust: `NetworkState.set_running_config_only(bool)`.
 * Python: `libnmstate.show_running_config()`.

Integration test cases enabled.

Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
Gris Ge 2022-02-11 11:10:05 +08:00
parent a5b1e59cb6
commit 8ed5100be5
13 changed files with 129 additions and 25 deletions

View File

@ -164,7 +164,10 @@ function run_tests {
if [ $TEST_TYPE == $TEST_TYPE_ALL ] || \
[ $TEST_TYPE == $TEST_TYPE_INTEG_RUST ];then
exec_cmd "cd $CONTAINER_WORKSPACE"
exec_cmd "cp /usr/bin/nmstatectl-rust /tmp"
exec_cmd "dnf remove python3-libnmstate -y"
exec_cmd "mv /tmp/nmstatectl-rust /usr/bin/nmstatectl"
exec_cmd "chmod +x /usr/bin/nmstatectl"
exec_cmd "
env \
PYTHONPATH=$CONTAINER_WORKSPACE/rust/src/python \
@ -177,6 +180,7 @@ function run_tests {
tests/integration/mac_vtap_test.py \
tests/integration/vxlan_test.py \
tests/integration/veth_test.py \
tests/integration/dynamic_ip_test.py \
${nmstate_pytest_extra_args}"
exec_cmd "
env \
@ -272,8 +276,8 @@ function run_tests {
PYTHONPATH=$CONTAINER_WORKSPACE/rust/src/python \
pytest \
$PYTEST_OPTIONS \
tests/integration/dynamic_ip_test.py \
-k 'not test_show_running_config_does_not_include_auto_config' \
tests/integration/nmstatectl_test.py \
-k 'running_config' \
${nmstate_pytest_extra_args}"
fi
}

View File

@ -49,6 +49,13 @@ fn main() {
.long("json")
.takes_value(false)
.help("Show state in json format"),
)
.arg(
clap::Arg::with_name("RUNNING_CONFIG_ONLY")
.short("r")
.long("running-config")
.takes_value(false)
.help("Show running configuration only"),
),
)
.subcommand(
@ -277,6 +284,9 @@ fn show(matches: &clap::ArgMatches) -> Result<String, CliError> {
if matches.is_present("KERNEL") {
net_state.set_kernel_only(true);
}
if matches.is_present("RUNNING_CONFIG_ONLY") {
net_state.set_running_config_only(true);
}
net_state.retrieve()?;
Ok(if let Some(ifname) = matches.value_of("IFNAME") {
let mut new_net_state = NetworkState::new();

View File

@ -22,6 +22,7 @@ const NMSTATE_FLAG_INCLUDE_SECRETS: u32 = 1 << 4;
const NMSTATE_FLAG_NO_COMMIT: u32 = 1 << 5;
// TODO
// const NMSTATE_FLAG_MEMORY_ONLY: u32 = 1 << 6;
const NMSTATE_FLAG_RUNNING_CONFIG_ONLY: u32 = 1 << 7;
const NMSTATE_PASS: c_int = 0;
const NMSTATE_FAIL: c_int = 1;
@ -60,6 +61,10 @@ pub extern "C" fn nmstate_net_state_retrieve(
net_state.set_include_secrets(true);
}
if (flags & NMSTATE_FLAG_RUNNING_CONFIG_ONLY) > 0 {
net_state.set_running_config_only(true);
}
// TODO: save log to the output pointer
match net_state.retrieve() {

View File

@ -61,6 +61,8 @@ pub struct NetworkState {
include_secrets: bool,
#[serde(skip)]
include_status_data: bool,
#[serde(skip)]
running_config_only: bool,
}
impl<'de> Deserialize<'de> for NetworkState {
@ -130,6 +132,16 @@ impl NetworkState {
self
}
// Query activated/running network configuration excluding:
// * IP address retrieved by DHCP or IPv6 auto configuration.
// * DNS client resolver retrieved by DHCP or IPv6 auto configuration.
// * Routes retrieved by DHCPv4 or IPv6 router advertisement.
// * LLDP neighbor information.
pub fn set_running_config_only(&mut self, value: bool) -> &mut Self {
self.running_config_only = value;
self
}
pub fn new() -> Self {
Default::default()
}
@ -151,7 +163,7 @@ impl NetworkState {
}
pub fn retrieve(&mut self) -> Result<&mut Self, NmstateError> {
let state = nispor_retrieve()?;
let state = nispor_retrieve(self.running_config_only)?;
if state.prop_list.contains(&"interfaces") {
self.interfaces = state.interfaces;
}
@ -162,7 +174,7 @@ impl NetworkState {
self.rules = state.rules;
}
if !self.kernel_only {
let nm_state = nm_retrieve()?;
let nm_state = nm_retrieve(self.running_config_only)?;
// TODO: Priority handling
self.update_state(&nm_state);
if ovsdb_is_running() {

View File

@ -42,13 +42,14 @@ impl From<(&nispor::IfaceState, &[nispor::IfaceFlags])> for InterfaceState {
pub(crate) fn np_iface_to_base_iface(
np_iface: &nispor::Iface,
running_config_only: bool,
) -> BaseInterface {
let base_iface = BaseInterface {
name: np_iface.name.to_string(),
state: (&np_iface.state, np_iface.flags.as_slice()).into(),
iface_type: np_iface_type_to_nmstate(&np_iface.iface_type),
ipv4: np_ipv4_to_nmstate(np_iface),
ipv6: np_ipv6_to_nmstate(np_iface),
ipv4: np_ipv4_to_nmstate(np_iface, running_config_only),
ipv6: np_ipv6_to_nmstate(np_iface, running_config_only),
mac_address: Some(np_iface.mac_address.to_uppercase()),
permanent_mac_address: get_permanent_mac_address(np_iface),
controller: np_iface.controller.as_ref().map(|c| c.to_string()),

View File

@ -2,6 +2,7 @@ use crate::{InterfaceIpAddr, InterfaceIpv4, InterfaceIpv6};
pub(crate) fn np_ipv4_to_nmstate(
np_iface: &nispor::Iface,
running_config_only: bool,
) -> Option<InterfaceIpv4> {
if let Some(np_ip) = &np_iface.ipv4 {
let mut ip = InterfaceIpv4::default();
@ -14,6 +15,9 @@ pub(crate) fn np_ipv4_to_nmstate(
if np_addr.valid_lft != "forever" {
ip.dhcp = true;
ip.prop_list.push("dhcp");
if running_config_only {
continue;
}
}
ip.addresses.push(InterfaceIpAddr {
ip: np_addr.address.clone(),
@ -37,6 +41,7 @@ pub(crate) fn np_ipv4_to_nmstate(
pub(crate) fn np_ipv6_to_nmstate(
np_iface: &nispor::Iface,
running_config_only: bool,
) -> Option<InterfaceIpv6> {
if let Some(np_ip) = &np_iface.ipv6 {
let mut ip = InterfaceIpv6::default();
@ -49,6 +54,9 @@ pub(crate) fn np_ipv6_to_nmstate(
if np_addr.valid_lft != "forever" {
ip.autoconf = true;
ip.prop_list.push("autoconf");
if running_config_only {
continue;
}
}
ip.addresses.push(InterfaceIpAddr {
ip: np_addr.address.clone(),

View File

@ -14,26 +14,30 @@ const IPV6_DEFAULT_GATEWAY: &str = "::/0";
const IPV4_EMPTY_NEXT_HOP_ADDRESS: &str = "0.0.0.0";
const IPV6_EMPTY_NEXT_HOP_ADDRESS: &str = "::";
pub(crate) fn get_routes(np_routes: &[nispor::Route]) -> Routes {
pub(crate) fn get_routes(
np_routes: &[nispor::Route],
running_config_only: bool,
) -> Routes {
let mut ret = Routes::new();
let mut running_routes = Vec::new();
for np_route in np_routes.iter().filter(|np_route| {
SUPPORTED_ROUTE_SCOPE.contains(&np_route.scope)
&& np_route.table != LOCAL_ROUTE_TABLE
&& np_route.oif.as_ref() != Some(&"lo".to_string())
}) {
if is_multipath(np_route) {
for flat_np_route in flat_multipath_route(np_route) {
running_routes.push(np_route_to_nmstate(&flat_np_route));
if !running_config_only {
let mut running_routes = Vec::new();
for np_route in np_routes.iter().filter(|np_route| {
SUPPORTED_ROUTE_SCOPE.contains(&np_route.scope)
&& np_route.table != LOCAL_ROUTE_TABLE
&& np_route.oif.as_ref() != Some(&"lo".to_string())
}) {
if is_multipath(np_route) {
for flat_np_route in flat_multipath_route(np_route) {
running_routes.push(np_route_to_nmstate(&flat_np_route));
}
} else if np_route.oif.is_some() {
running_routes.push(np_route_to_nmstate(np_route));
}
} else if np_route.oif.is_some() {
running_routes.push(np_route_to_nmstate(np_route));
}
ret.running = Some(running_routes);
}
ret.running = Some(running_routes);
let mut config_routes = Vec::new();
for np_route in np_routes.iter().filter(|np_route| {
SUPPORTED_ROUTE_SCOPE.contains(&np_route.scope)

View File

@ -21,7 +21,9 @@ use crate::{
NmstateError, OvsInterface, UnknownInterface,
};
pub(crate) fn nispor_retrieve() -> Result<NetworkState, NmstateError> {
pub(crate) fn nispor_retrieve(
running_config_only: bool,
) -> Result<NetworkState, NmstateError> {
let mut net_state = NetworkState::new();
net_state.prop_list.push("interfaces");
net_state.prop_list.push("routes");
@ -29,7 +31,8 @@ pub(crate) fn nispor_retrieve() -> Result<NetworkState, NmstateError> {
let np_state = nispor::NetState::retrieve().map_err(np_error_to_nmstate)?;
for (_, np_iface) in np_state.ifaces.iter() {
let mut base_iface = np_iface_to_base_iface(np_iface);
let mut base_iface =
np_iface_to_base_iface(np_iface, running_config_only);
// The `ovs-system` is reserved for OVS kernel datapath
if np_iface.name == "ovs-system" {
continue;
@ -106,7 +109,7 @@ pub(crate) fn nispor_retrieve() -> Result<NetworkState, NmstateError> {
net_state.append_interface_data(iface);
}
set_controller_type(&mut net_state.interfaces);
net_state.routes = get_routes(&np_state.routes);
net_state.routes = get_routes(&np_state.routes, running_config_only);
net_state.rules = get_route_rules(&np_state.rules);
Ok(net_state)

View File

@ -28,7 +28,9 @@ use crate::{
VrfInterface, VxlanInterface,
};
pub(crate) fn nm_retrieve() -> Result<NetworkState, NmstateError> {
pub(crate) fn nm_retrieve(
running_config_only: bool,
) -> Result<NetworkState, NmstateError> {
let mut net_state = NetworkState::new();
net_state.prop_list = vec!["interfaces", "dns"];
let nm_api = NmApi::new().map_err(nm_error_to_nmstate)?;
@ -180,6 +182,9 @@ pub(crate) fn nm_retrieve() -> Result<NetworkState, NmstateError> {
}
net_state.dns = retrieve_dns_info(&nm_api, &net_state.interfaces)?;
if running_config_only {
net_state.dns.running = None;
}
set_ovs_iface_controller_info(&mut net_state.interfaces);

View File

@ -3,6 +3,7 @@ from .netapplier import apply
from .netapplier import rollback
from .netapplier import commit
from .netinfo import show
from .netinfo import show_running_config
from .prettystate import PrettyState
__all__ = [
@ -11,6 +12,7 @@ __all__ = [
"rollback",
"commit",
"show",
"show_running_config",
"PrettyState",
]

View File

@ -46,11 +46,15 @@ NMSTATE_FLAG_INCLUDE_STATUS_DATA = 1 << 3
NMSTATE_FLAG_INCLUDE_SECRETS = 1 << 4
NMSTATE_FLAG_NO_COMMIT = 1 << 5
# NMSTATE_FLAG_MEMORY_ONLY = 1 << 6
NMSTATE_FLAG_RUNNING_CONFIG_ONLY = 1 << 7
NMSTATE_PASS = 0
def retrieve_net_state_json(
kernel_only=False, include_status_data=False, include_secrets=False
kernel_only=False,
include_status_data=False,
include_secrets=False,
running_config_only=False,
):
c_err_msg = c_char_p()
c_err_kind = c_char_p()
@ -63,6 +67,8 @@ def retrieve_net_state_json(
flags |= NMSTATE_FLAG_INCLUDE_STATUS_DATA
if include_secrets:
flags |= NMSTATE_FLAG_INCLUDE_SECRETS
if running_config_only:
flags |= NMSTATE_FLAG_RUNNING_CONFIG_ONLY
rc = lib.nmstate_net_state_retrieve(
flags,

View File

@ -27,3 +27,12 @@ def show(
include_secrets=include_secrets,
)
)
def show_running_config(include_secrets=False):
return json.loads(
retrieve_net_state_json(
include_secrets=include_secrets,
running_config_only=True,
)
)

View File

@ -0,0 +1,35 @@
#
# Copyright (c) 2020 Red Hat, Inc.
#
# This file is part of nmstate
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
from collections.abc import Mapping
PASSWORD_HID_BY_NMSTATE = "<_password_hid_by_nmstate>"
def hide_the_secrets(state):
if isinstance(state, Mapping):
for key, value in state.items():
if isinstance(value, Mapping) or isinstance(value, list):
hide_the_secrets(value)
elif key.endswith("password") and isinstance(value, str):
state[key] = PASSWORD_HID_BY_NMSTATE
elif isinstance(state, list):
for value in state:
hide_the_secrets(value)