Internal ID: FO037
Authors: Simon Chopin, Lukas Märdian
Abstract
Libnetplan is a component of the netplan.io project that contains the logic for data parsing, validation and generation. It is build as a dynamic .so library that can be used from different binaries (like netplan’s “generate”, “netplan-dbus”, the “netplan apply/try/get/set/…” CLI via the corresponding Python bindings or external applications like the NetworkManager, using the netplan backend). We want to have properly defined API and Python bindings for this that is consistent and future proof.
Rationale
Libnetplan was introduced by ripping out a bunch of code from the netplan’s “generate” binary, which lead to interfaces that are not really thought through, with inconsistent naming of API functions and exporting of unnecessary internal symbols.
We want to fix that and design the API in a future-proof way.
Specification
General convention
The general rule for our API is to export as few public symbols as necessary, following the convention of calling them “netplan_OBJECT_ACTION” using a “_” prefix for symbols that are supposed to be used internally only (e.g. by netplan CLI). The ACTION should resemble the stanza name used in the YAML structure (if any) as closely as possible. Some examples:
- netplan_state_write_yaml()
- netplan_state_clear()
- netplan_parser_new()
- netplan_parser_load_yaml()
- netplan_parser_load_yaml_from_fd()
- netplan_parser_load_keyfile()
- netplan_netdef_get_id()
- _netplan_netdef_get_embedded_switch_mode() #for internal use only
If any strings are to be returned, we want the caller to do all allocation and free’ing of any memory needed, so that libnetplan itself can write the output strings to an existing buffer. The function would return a ‘ssize_t’ value that would either indicate the number of bytes written to the buffer, or an error code in the negative range. If the provided buffer is too small, the function should return a well-defined error code. This way (lib-)netplan and its caller are most independent and the memory management/allocation technology can be changed independently without worrying about breaking any callers (think GLib vs Glibc memory allocation, or switching over netplan to another language like Rust, using another way of memory allocation).
When we need to return complex errors beyond the error code, we use the GError** pattern from GLib, but with a custom type (that will for now internally be typedef’d to GError to avoid breaking ABI), with the following functions available to the user:
- void netplan_error_clear(NetplanError** error);
- ssize_t netplan_error_message(NetplanError* error, char* buf, size_t buf_size);
- uint64 netplan_error_code(NetplanError* error); (uint64 to encode both domain and code)
Object and API definition
Netplan objects to be used publicly/externally.
-
NetplanNetDefinition API
- Any data is supposed to be retrieved via accessor functions (getters/setters), libnetplan’s data structs shall never be used externally.
- netplan_netdef_get_id()
- netplan_netdef_get_type()
- netplan_netdef_get_backend()
- netplan_netdef_get_bridge/bond/peer/link/sriov_link()
- Shall return a pointer to a netdef instead of a string
- netplan_netdef_get_filepath()
- e.g. YAML filename the the netdef data has been read from
- netplan_netdef_get_output_filename()
- e.g. for NM keyfile, to be written
- Matching logic → do not expose all the individual data (like: mac, ifname, drivers), but only provide a “match_interface(netdef, ifname)” function, returning True/False if the given netdef matches that interface
- netplan_netdef_get_set_name()
- netplan_netdef_match_interface()
- netplan_netdef_has_match()
- For internal use only, i.e. not part of the public API (e.g. netplan CLI / python bindings):
- _netplan_netdef_get_critical/optional()
- _netplan_netdef_get_vf_count/vlan_filter/embedded_switch_mode/delay_rebind
- netplan_netdef_write_yaml()
-
NetplanParser API
- A class to be used for parsing a bunch of netplan YAML files from the filesystem, carrying a hashmap of “parsed_defs” amongst others. The netdef data in this class has not been validated and might contain invalid user input
- netplan_parser_new()
- netplan_parser_reset()
- netplan_parser_clear()
- netplan_parser_load_yaml()
- netplan_parser_load_keyfile()
-
NetplanState API
- A class to be used for importing the NetplanParser class’ results, validating it to avoid user input/configuration conflicts. It contains an ordered list of all netdefs and a hashmap of netplan_id → pointers to the very same netdef data.
- netplan_state_new()
- netplan_state_import_parser_results()
- netplan_state_reset()
- netplan_state_clear()
- netplan_state_get_backend()
- netplan_state_get_netdefs_size()
- netplan_state_get_netdef()
- netplan_state_dump_yaml()
- netplan_state_finish_nm_write()
- netplan_state_finish_ovs_write()
- netplan_state_finish_sriov_write()
Additional API for generic netplan utils
- netplan_delete_connection()
- netplan_generate()
- netplan_get_id_from_nm_filename()
- netplan_get_filename_by_id()
Cleanup of public headers
At some point after the new API has been widely available, we want to bump the SONAME and break the old/legacy API, this is when we want to clean up the legacy API by dropping the old symbols. Until we reach that point, we have a compatibility wrapper that is located in src/abi_compat.c and ./abicompat.lds to accommodate for global variables and legacy API functions/symbols.
- Remove from public headers write_netplan_conf (but keep it in abi compat)
- Remove everything below “Old API comments”
- Also check the “After soname bump (ABI break)” section of ./TODO for more cleanup hints
Further Information
- https://github.com/canonical/netplan/pull/255
- https://github.com/canonical/netplan/pull/254
- https://github.com/canonical/netplan/pull/262
- https://github.com/canonical/netplan/pull/252
- https://github.com/canonical/netplan/pull/251
- https://github.com/canonical/netplan/pull/250
- https://github.com/canonical/netplan/pull/249
- https://github.com/canonical/netplan/pull/241
- https://github.com/canonical/netplan/pull/239