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:
parent
a5b1e59cb6
commit
8ed5100be5
@ -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
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
@ -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()),
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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",
|
||||
]
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
35
rust/src/python/libnmstate/state.py
Normal file
35
rust/src/python/libnmstate/state.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user