From ad2bfa136290e72cdfd4b7877b49b3fc07203f9c Mon Sep 17 00:00:00 2001 From: Gris Ge Date: Tue, 21 Feb 2023 16:26:22 +0800 Subject: [PATCH] clib: Introduce YAML support Allowing both YAML and JSON input, the output format will matching input format. For `nmstate_net_state_retrieve()`, user can use `NMSTATE_FLAG_YAML_OUTPUT` flag to instruct the output to be YAML format. Signed-off-by: Gris Ge --- rust/src/clib/Cargo.toml | 1 + rust/src/clib/apply.rs | 2 +- rust/src/clib/gen_conf.rs | 52 +++++++----- rust/src/clib/nmstate.h.in | 55 +++++++------ rust/src/clib/policy.rs | 57 ++++++++----- rust/src/clib/query.rs | 49 ++++++++---- .../{nmpolicy_test.c => nmpolicy_json_test.c} | 5 ++ rust/src/clib/test/nmpolicy_yaml_test.c | 80 +++++++++++++++++++ .../{nmstate_test.c => nmstate_json_test.c} | 5 ++ rust/src/clib/test/nmstate_yaml_test.c | 34 ++++++++ rust/src/lib/Cargo.toml | 3 + rust/src/lib/net_state.rs | 14 +++- 12 files changed, 274 insertions(+), 83 deletions(-) rename rust/src/clib/test/{nmpolicy_test.c => nmpolicy_json_test.c} (96%) create mode 100644 rust/src/clib/test/nmpolicy_yaml_test.c rename rust/src/clib/test/{nmstate_test.c => nmstate_json_test.c} (87%) create mode 100644 rust/src/clib/test/nmstate_yaml_test.c diff --git a/rust/src/clib/Cargo.toml b/rust/src/clib/Cargo.toml index 97e4128c..ed391b3a 100644 --- a/rust/src/clib/Cargo.toml +++ b/rust/src/clib/Cargo.toml @@ -16,6 +16,7 @@ crate-type = ["cdylib", "staticlib"] nmstate = { path = "../lib", default-features = false } libc = "0.2.74" serde_json = "1.0" +serde_yaml = "0.9" log = "0.4.17" serde = { version = "1.0.137", features = ["derive"] } once_cell = "1.12.0" diff --git a/rust/src/clib/apply.rs b/rust/src/clib/apply.rs index 9a0d6fbc..67d39730 100644 --- a/rust/src/clib/apply.rs +++ b/rust/src/clib/apply.rs @@ -74,7 +74,7 @@ pub extern "C" fn nmstate_net_state_apply( }; let mut net_state = - match nmstate::NetworkState::new_from_json(net_state_str) { + match nmstate::NetworkState::new_from_yaml(net_state_str) { Ok(n) => n, Err(e) => { unsafe { diff --git a/rust/src/clib/gen_conf.rs b/rust/src/clib/gen_conf.rs index f63fb7b0..1ad7156b 100644 --- a/rust/src/clib/gen_conf.rs +++ b/rust/src/clib/gen_conf.rs @@ -68,7 +68,7 @@ pub extern "C" fn nmstate_generate_configurations( } }; - let net_state = match nmstate::NetworkState::new_from_json(net_state_str) { + let net_state = match nmstate::NetworkState::new_from_yaml(net_state_str) { Ok(n) => n, Err(e) => { unsafe { @@ -80,28 +80,44 @@ pub extern "C" fn nmstate_generate_configurations( } }; + let input_is_json = + serde_json::from_str::(net_state_str).is_ok(); let result = net_state.gen_conf(); unsafe { *log = CString::new(logger.drain(now)).unwrap().into_raw(); } match result { - Ok(s) => match serde_json::to_string(&s) { - Ok(cfgs) => unsafe { - *configs = CString::new(cfgs).unwrap().into_raw(); - NMSTATE_PASS - }, - Err(e) => unsafe { - *err_msg = - CString::new(format!("serde_json::to_string failure: {e}")) - .unwrap() - .into_raw(); - *err_kind = - CString::new(format!("{}", nmstate::ErrorKind::Bug)) - .unwrap() - .into_raw(); - NMSTATE_FAIL - }, - }, + Ok(s) => { + let serialize = if input_is_json { + serde_json::to_string(&s).map_err(|e| { + nmstate::NmstateError::new( + nmstate::ErrorKind::Bug, + format!("Failed to convert state {s:?} to JSON: {e}"), + ) + }) + } else { + serde_yaml::to_string(&s).map_err(|e| { + nmstate::NmstateError::new( + nmstate::ErrorKind::Bug, + format!("Failed to convert state {s:?} to YAML: {e}"), + ) + }) + }; + + match serialize { + Ok(cfgs) => unsafe { + *configs = CString::new(cfgs).unwrap().into_raw(); + NMSTATE_PASS + }, + Err(e) => unsafe { + *err_msg = + CString::new(e.msg().to_string()).unwrap().into_raw(); + *err_kind = + CString::new(e.kind().to_string()).unwrap().into_raw(); + NMSTATE_FAIL + }, + } + } Err(e) => { unsafe { *err_msg = CString::new(e.msg()).unwrap().into_raw(); diff --git a/rust/src/clib/nmstate.h.in b/rust/src/clib/nmstate.h.in index 0879d47e..391477fd 100644 --- a/rust/src/clib/nmstate.h.in +++ b/rust/src/clib/nmstate.h.in @@ -1,19 +1,4 @@ -/* - * Copyright 2021 Red Hat - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - +// SPDX-License-Identifier: Apache-2.0 #ifndef _LIBNMSTATE_H_ #define _LIBNMSTATE_H_ @@ -44,6 +29,7 @@ extern "C" { #define NMSTATE_FLAG_NO_COMMIT 1 << 5 #define NMSTATE_FLAG_MEMORY_ONLY 1 << 6 #define NMSTATE_FLAG_RUNNING_CONFIG_ONLY 1 << 7 +#define NMSTATE_FLAG_YAML_OUTPUT 1 << 8 /** * nmstate_net_state_retrieve - Retrieve network state @@ -52,7 +38,7 @@ extern "C" { * 0.1 * * Description: - * Retrieve network state in the format of JSON. + * Retrieve network state in the format of JSON or YAML. * * @flags: * Flags for special use cases: @@ -60,6 +46,13 @@ extern "C" { * No flag * * NMSTATE_FLAG_KERNEL_ONLY * Do not use external plugins, show kernel status only. + * * NMSTATE_FLAG_INCLUDE_SECRETS + * No not hide sercerts like password. + * * NMSTATE_FLAG_RUNNING_CONFIG_ONLY + * Only include running config excluding running status like auto + * IP addresses and routes, LLDP neighbors. + * * NMSTATE_FLAG_YAML_OUTPUT + * Show the state in YAML format * @state: * Output pointer of char array for network state in json format. * The memory should be freed by nmstate_net_state_free(). @@ -90,7 +83,7 @@ int nmstate_net_state_retrieve(uint32_t flags, char **state, char **log, * 0.1 * * Description: - * Apply network state in the format of JSON. + * Apply network state in the format of JSON or YAML. * * @flags: * Flags for special use cases: @@ -98,8 +91,12 @@ int nmstate_net_state_retrieve(uint32_t flags, char **state, char **log, * No flag * * NMSTATE_FLAG_KERNEL_ONLY * Do not use external plugins, apply to kernel only. + * * NMSTATE_FLAG_NO_VERIFY + * Do not verify state after applied * * NMSTATE_FLAG_NO_COMMIT * Do not commit new state after verification + * * NMSTATE_FLAG_MEMORY_ONLY + * No not store network state to persistent. * @state: * Pointer of char array for network state in json format. * @log: @@ -119,7 +116,8 @@ int nmstate_net_state_retrieve(uint32_t flags, char **state, char **log, * * NMSTATE_FAIL * On failure. */ -int nmstate_net_state_apply(uint32_t flags, const char *state, uint32_t rollback_timeout, char **log, +int nmstate_net_state_apply(uint32_t flags, const char *state, + uint32_t rollback_timeout, char **log, char **err_kind, char **err_msg); /** @@ -151,8 +149,8 @@ int nmstate_net_state_apply(uint32_t flags, const char *state, uint32_t rollback * * NMSTATE_FAIL * On failure. */ -int nmstate_checkpoint_commit(const char *checkpoint, char **log, char **err_kind, - char **err_msg); +int nmstate_checkpoint_commit(const char *checkpoint, char **log, + char **err_kind, char **err_msg); /** * nmstate_checkpoint_rollback - Rollback the checkpoint @@ -183,8 +181,8 @@ int nmstate_checkpoint_commit(const char *checkpoint, char **log, char **err_kin * * NMSTATE_FAIL * On failure. */ -int nmstate_checkpoint_rollback(const char *checkpoint, char **log, char **err_kind, - char **err_msg); +int nmstate_checkpoint_rollback(const char *checkpoint, char **log, + char **err_kind, char **err_msg); /** * nmstate_generate_configurations - Generate network configurations @@ -199,9 +197,10 @@ int nmstate_checkpoint_rollback(const char *checkpoint, char **log, char **err_k * as value. * * @state: - * Pointer of char array for network state in json format. + * Pointer of char array for network state in JSON or YAML format. * @configs: - * Output pointer of char array for network configures in json format. + * Output pointer of char array for network configures in JSON or + * YAML(depend on which format you use in @state) format. * The memory should be freed by nmstate_net_state_free(). * @log: * Output pointer of char array for logging. @@ -231,14 +230,14 @@ int nmstate_generate_configurations(const char *state, char **configs, * 2.2 * * Description: - * Generate new network state from policy again specifed state + * Generate new network state from policy again specified state * * @policy: - * Pointer of char array for network policy in json format. + * Pointer of char array for network policy in JSON/YAML format. * @current_state: * Pointer of char array for current network state. * @state: - * Output pointer of char array for network state in json format. + * Output pointer of char array for network state in JSON/YAML format. * The memory should be freed by nmstate_net_state_free(). * @log: * Output pointer of char array for logging. diff --git a/rust/src/clib/policy.rs b/rust/src/clib/policy.rs index ec8c46c1..ea7dd036 100644 --- a/rust/src/clib/policy.rs +++ b/rust/src/clib/policy.rs @@ -67,6 +67,13 @@ pub extern "C" fn nmstate_net_state_from_policy( } }; + let input_is_json = + if let Ok(policy_str) = unsafe { CStr::from_ptr(policy) }.to_str() { + serde_json::from_str::(policy_str).is_ok() + } else { + false + }; + let mut policy = match deserilize_from_c_char::( policy, err_kind, err_msg, ) { @@ -86,23 +93,37 @@ pub extern "C" fn nmstate_net_state_from_policy( } match result { - Ok(s) => match serde_json::to_string(&s) { - Ok(state_str) => unsafe { - *state = CString::new(state_str).unwrap().into_raw(); - NMSTATE_PASS - }, - Err(e) => unsafe { - *err_msg = - CString::new(format!("serde_json::to_string failure: {e}")) - .unwrap() - .into_raw(); - *err_kind = - CString::new(format!("{}", nmstate::ErrorKind::Bug)) - .unwrap() - .into_raw(); - NMSTATE_FAIL - }, - }, + Ok(s) => { + let serialize = if input_is_json { + serde_json::to_string(&s).map_err(|e| { + nmstate::NmstateError::new( + nmstate::ErrorKind::Bug, + format!("Failed to convert state {s:?} to JSON: {e}"), + ) + }) + } else { + serde_yaml::to_string(&s).map_err(|e| { + nmstate::NmstateError::new( + nmstate::ErrorKind::Bug, + format!("Failed to convert state {s:?} to YAML: {e}"), + ) + }) + }; + + match serialize { + Ok(state_str) => unsafe { + *state = CString::new(state_str).unwrap().into_raw(); + NMSTATE_PASS + }, + Err(e) => unsafe { + *err_msg = + CString::new(e.msg().to_string()).unwrap().into_raw(); + *err_kind = + CString::new(e.kind().to_string()).unwrap().into_raw(); + NMSTATE_FAIL + }, + } + } Err(e) => { unsafe { *err_msg = CString::new(e.msg()).unwrap().into_raw(); @@ -144,7 +165,7 @@ where } }; - match serde_json::from_str(content_str) { + match serde_yaml::from_str(content_str) { Ok(n) => Some(n), Err(e) => { unsafe { diff --git a/rust/src/clib/query.rs b/rust/src/clib/query.rs index a24b9c83..12e44d05 100644 --- a/rust/src/clib/query.rs +++ b/rust/src/clib/query.rs @@ -14,6 +14,7 @@ pub(crate) const NMSTATE_FLAG_INCLUDE_SECRETS: u32 = 1 << 4; pub(crate) const NMSTATE_FLAG_NO_COMMIT: u32 = 1 << 5; pub(crate) const NMSTATE_FLAG_MEMORY_ONLY: u32 = 1 << 6; pub(crate) const NMSTATE_FLAG_RUNNING_CONFIG_ONLY: u32 = 1 << 7; +pub(crate) const NMSTATE_FLAG_YAML_OUTPUT: u32 = 1 << 8; #[allow(clippy::not_unsafe_ptr_arg_deref)] #[no_mangle] @@ -72,23 +73,37 @@ pub extern "C" fn nmstate_net_state_retrieve( } match result { - Ok(s) => match serde_json::to_string(&s) { - Ok(state_str) => unsafe { - *state = CString::new(state_str).unwrap().into_raw(); - NMSTATE_PASS - }, - Err(e) => unsafe { - *err_msg = - CString::new(format!("serde_json::to_string failure: {e}")) - .unwrap() - .into_raw(); - *err_kind = - CString::new(format!("{}", nmstate::ErrorKind::Bug)) - .unwrap() - .into_raw(); - NMSTATE_FAIL - }, - }, + Ok(s) => { + let serialize = if (flags & NMSTATE_FLAG_YAML_OUTPUT) > 0 { + serde_yaml::to_string(&s).map_err(|e| { + nmstate::NmstateError::new( + nmstate::ErrorKind::Bug, + format!("Failed to convert state {s:?} to YAML: {e}"), + ) + }) + } else { + serde_json::to_string(&s).map_err(|e| { + nmstate::NmstateError::new( + nmstate::ErrorKind::Bug, + format!("Failed to convert state {s:?} to JSON: {e}"), + ) + }) + }; + + match serialize { + Ok(state_str) => unsafe { + *state = CString::new(state_str).unwrap().into_raw(); + NMSTATE_PASS + }, + Err(e) => unsafe { + *err_msg = + CString::new(e.msg().to_string()).unwrap().into_raw(); + *err_kind = + CString::new(e.kind().to_string()).unwrap().into_raw(); + NMSTATE_FAIL + }, + } + } Err(e) => { unsafe { *err_msg = CString::new(e.msg()).unwrap().into_raw(); diff --git a/rust/src/clib/test/nmpolicy_test.c b/rust/src/clib/test/nmpolicy_json_test.c similarity index 96% rename from rust/src/clib/test/nmpolicy_test.c rename to rust/src/clib/test/nmpolicy_json_test.c index 7a71a5f5..8a0444d4 100644 --- a/rust/src/clib/test/nmpolicy_test.c +++ b/rust/src/clib/test/nmpolicy_json_test.c @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include #include #include #include @@ -91,6 +94,8 @@ int main(void) { rc = EXIT_FAILURE; } + assert(state[0] == '{'); + nmstate_cstring_free(state); nmstate_cstring_free(err_kind); nmstate_cstring_free(err_msg); diff --git a/rust/src/clib/test/nmpolicy_yaml_test.c b/rust/src/clib/test/nmpolicy_yaml_test.c new file mode 100644 index 00000000..7984f509 --- /dev/null +++ b/rust/src/clib/test/nmpolicy_yaml_test.c @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +#include + +int main(void) { + int rc = EXIT_SUCCESS; + const char *policy = "\ +capture:\n\ + default-gw: override me with the cache\n\ + base-iface: >\n\ + interfaces.name == capture.default-gw.routes.running.0.next-hop-interface\n\ + base-iface-routes: >\n\ + routes.running.next-hop-interface ==\n\ + capture.default-gw.routes.running.0.next-hop-interface\n\ + bridge-routes: >\n\ + capture.base-iface-routes | routes.running.next-hop-interface:=\"br1\"\n\ +desired:\n\ + interfaces:\n\ + - name: br1\n\ + description: Linux bridge with base interface as a port\n\ + type: linux-bridge\n\ + state: up\n\ + bridge:\n\ + options:\n\ + stp:\n\ + enabled: false\n\ + port:\n\ + - name: '{{ capture.base-iface.interfaces.0.name }}'\n\ + ipv4: '{{ capture.base-iface.interfaces.0.ipv4 }}'\n\ + routes:\n\ + config: '{{ capture.bridge-routes.routes.running }}'"; + const char *current_state = "\ +interfaces:\n\ +- name: eth1\n\ + type: ethernet\n\ + state: up\n\ + mac-address: 1c:c1:0c:32:3b:ff\n\ + ipv4:\n\ + address:\n\ + - ip: 192.0.2.251\n\ + prefix-length: 24\n\ + dhcp: false\n\ + enabled: true\n\ +routes:\n\ + config:\n\ + - destination: 0.0.0.0/0\n\ + next-hop-address: 192.0.2.1\n\ + next-hop-interface: eth1\n\ + running:\n\ + - destination: 0.0.0.0/0\n\ + next-hop-address: 192.0.2.1\n\ + next-hop-interface: eth1"; + char *state = NULL; + char *err_kind = NULL; + char *err_msg = NULL; + char *log = NULL; + + if (nmstate_net_state_from_policy(policy, current_state, &state, &log, + &err_kind, &err_msg) == NMSTATE_PASS) + { + printf("%s\n", state); + } else { + printf("%s: %s\n", err_kind, err_msg); + rc = EXIT_FAILURE; + } + + assert(state[0] != '{'); + + nmstate_cstring_free(state); + nmstate_cstring_free(err_kind); + nmstate_cstring_free(err_msg); + nmstate_cstring_free(log); + exit(rc); +} diff --git a/rust/src/clib/test/nmstate_test.c b/rust/src/clib/test/nmstate_json_test.c similarity index 87% rename from rust/src/clib/test/nmstate_test.c rename to rust/src/clib/test/nmstate_json_test.c index 0e79cb15..1bfbcda7 100644 --- a/rust/src/clib/test/nmstate_test.c +++ b/rust/src/clib/test/nmstate_json_test.c @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include #include #include #include @@ -21,6 +24,8 @@ int main(void) { rc = EXIT_FAILURE; } + assert(state[0] == '{'); + nmstate_cstring_free(state); nmstate_cstring_free(err_kind); nmstate_cstring_free(err_msg); diff --git a/rust/src/clib/test/nmstate_yaml_test.c b/rust/src/clib/test/nmstate_yaml_test.c new file mode 100644 index 00000000..de0f2486 --- /dev/null +++ b/rust/src/clib/test/nmstate_yaml_test.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include + +#include + +int main(void) { + int rc = EXIT_SUCCESS; + char *state = NULL; + char *err_kind = NULL; + char *err_msg = NULL; + char *log = NULL; + uint32_t flag = NMSTATE_FLAG_KERNEL_ONLY | NMSTATE_FLAG_YAML_OUTPUT; + + if (nmstate_net_state_retrieve(flag, &state, &log, &err_kind, &err_msg) + == NMSTATE_PASS) { + printf("%s\n", state); + } else { + printf("%s: %s\n", err_kind, err_msg); + rc = EXIT_FAILURE; + } + + assert(state[0] != '{'); + + nmstate_cstring_free(state); + nmstate_cstring_free(err_kind); + nmstate_cstring_free(err_msg); + nmstate_cstring_free(log); + exit(rc); +} diff --git a/rust/src/lib/Cargo.toml b/rust/src/lib/Cargo.toml index a27d0e1a..0142026d 100644 --- a/rust/src/lib/Cargo.toml +++ b/rust/src/lib/Cargo.toml @@ -15,6 +15,9 @@ edition = "2018" [lib] path = "lib.rs" +[dependencies] +serde_yaml = "0.9" + [dependencies.nispor] version = "1.2.9" optional = true diff --git a/rust/src/lib/net_state.rs b/rust/src/lib/net_state.rs index 8ab79642..fe5fea78 100644 --- a/rust/src/lib/net_state.rs +++ b/rust/src/lib/net_state.rs @@ -274,7 +274,19 @@ impl NetworkState { Ok(s) => Ok(s), Err(e) => Err(NmstateError::new( ErrorKind::InvalidArgument, - format!("Invalid json string: {e}"), + format!("Invalid JSON string: {e}"), + )), + } + } + + /// Wrapping function of [serde_yaml::from_str()] with error mapped to + /// [NmstateError]. + pub fn new_from_yaml(net_state_yaml: &str) -> Result { + match serde_yaml::from_str(net_state_yaml) { + Ok(s) => Ok(s), + Err(e) => Err(NmstateError::new( + ErrorKind::InvalidArgument, + format!("Invalid YAML string: {e}"), )), } } -- 2.39.2