gen_conf: Allowing generate configurations by desire state
Introduced below allowing user to get the configurations of desire state. User can save the configuration in their preferred way. * `libnmstate.generate_configurations(desire_state)` * `nmstatectl gc state.yml` It will return a dictionary with: * Key: plugin name * Value: list of tuple(file name and file content) for configurations used by that backend. Example for `nmstatectl gc eth1_up.yml` ```yaml --- NetworkManager: - - eth1.nmconnection - '[connection] id=eth1 uuid=091eaa84-f9da-408a-8144-3da890967b7b type=ethernet autoconnect-slaves=1 interface-name=eth1 permissions= [ethernet] mac-address-blacklist= mtu=1500 [ipv4] dhcp-client-id=mac dns-search= method=disabled [ipv6] addr-gen-mode=eui64 dhcp-duid=ll dhcp-iaid=mac dns-search= method=disabled [proxy] ' ``` Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
parent
fd21172d50
commit
627af31725
@ -13,6 +13,8 @@ nmstatectl \- A nmstate command line tool
|
||||
.br
|
||||
.B nmstatectl edit \fR[\fIINTERFACE_NAME\fR] [\fIOPTIONS\fR]
|
||||
.br
|
||||
.B nmstatectl gc \fR[\fISTATE_FILE_PATH\fR] [\fIOPTIONS\fR]
|
||||
.br
|
||||
.B nmstatectl rollback \fR[\fICHECKPOINT_PATH\fR]
|
||||
.br
|
||||
.B nmstatectl commit \fR[\fICHECKPOINT_PATH\fR]
|
||||
@ -89,6 +91,18 @@ decide whether rollback to previous (before \fB"nmstatectl set/edit"\fR) state.
|
||||
rollback the network state from specified checkpoint file. \fBnmstatectl\fR
|
||||
will take the latest checkpoint if not defined as argument.
|
||||
.PP
|
||||
|
||||
.B gc
|
||||
|
||||
.RS
|
||||
Generates configuration files for specified network state file(s). The output
|
||||
will be dictinary with plugin name as key and an tuple as value.
|
||||
The tuple will holding configuration file name and configuration content.
|
||||
|
||||
The generated configuration is not saved into system, users have to do it
|
||||
by themselves after refering to the network backend.
|
||||
.RE
|
||||
|
||||
.B commit
|
||||
.RS
|
||||
commit the current network state. \fBnmstatectl\fR will take the latest
|
||||
|
@ -27,7 +27,7 @@ from .netapplier import commit
|
||||
from .netapplier import rollback
|
||||
from .netinfo import show
|
||||
from .netinfo import show_running_config
|
||||
|
||||
from .nmstate import generate_configurations
|
||||
from .prettystate import PrettyState
|
||||
|
||||
|
||||
@ -38,6 +38,7 @@ __all__ = [
|
||||
"apply",
|
||||
"commit",
|
||||
"error",
|
||||
"generate_configurations",
|
||||
"rollback",
|
||||
"schema",
|
||||
"show",
|
||||
|
@ -40,9 +40,10 @@ class DnsState:
|
||||
else:
|
||||
self._dns_state = des_dns_state
|
||||
self._validate()
|
||||
self._config_changed = _is_dns_config_changed(
|
||||
des_dns_state, cur_dns_state
|
||||
)
|
||||
if cur_dns_state:
|
||||
self._config_changed = _is_dns_config_changed(
|
||||
des_dns_state, cur_dns_state
|
||||
)
|
||||
self._cur_dns_state = deepcopy(cur_dns_state) if cur_dns_state else {}
|
||||
|
||||
@property
|
||||
|
@ -434,7 +434,10 @@ class BaseIface:
|
||||
|
||||
def store_route_metadata(self, route_metadata):
|
||||
for family, routes in route_metadata.items():
|
||||
self.raw[family][BaseIface.ROUTES_METADATA] = routes
|
||||
try:
|
||||
self.raw[family][BaseIface.ROUTES_METADATA] = routes
|
||||
except KeyError:
|
||||
self.raw[family] = {BaseIface.ROUTES_METADATA: routes}
|
||||
|
||||
def store_route_rule_metadata(self, route_rule_metadata):
|
||||
for family, rules in route_rule_metadata.items():
|
||||
|
@ -81,9 +81,16 @@ class Ifaces:
|
||||
also responsible to handle desire vs current state related tasks.
|
||||
"""
|
||||
|
||||
def __init__(self, des_iface_infos, cur_iface_infos, save_to_disk=True):
|
||||
def __init__(
|
||||
self,
|
||||
des_iface_infos,
|
||||
cur_iface_infos,
|
||||
save_to_disk=True,
|
||||
gen_conf_mode=False,
|
||||
):
|
||||
self._save_to_disk = save_to_disk
|
||||
self._des_iface_infos = des_iface_infos
|
||||
self._gen_conf_mode = gen_conf_mode
|
||||
self._cur_kernel_ifaces = {}
|
||||
self._kernel_ifaces = {}
|
||||
self._user_space_ifaces = _UserSpaceIfaces()
|
||||
@ -102,6 +109,8 @@ class Ifaces:
|
||||
if des_iface_infos:
|
||||
for iface_info in des_iface_infos:
|
||||
iface = BaseIface(iface_info, save_to_disk)
|
||||
if not iface.is_up and self._gen_conf_mode:
|
||||
continue
|
||||
if iface.type == InterfaceType.UNKNOWN:
|
||||
cur_ifaces = self._get_cur_ifaces(iface.name)
|
||||
if len(cur_ifaces) > 1:
|
||||
@ -119,6 +128,8 @@ class Ifaces:
|
||||
if iface_info.get(Interface.TYPE) is None:
|
||||
if cur_iface:
|
||||
iface_info[Interface.TYPE] = cur_iface.type
|
||||
elif gen_conf_mode:
|
||||
iface_info[Interface.TYPE] = InterfaceType.ETHERNET
|
||||
elif iface.is_up:
|
||||
raise NmstateValueError(
|
||||
f"Interface {iface.name} has no type defined "
|
||||
@ -634,26 +645,59 @@ class Ifaces:
|
||||
# All the user space interface already has interface type defined.
|
||||
# And user space interface cannot be port of other interface.
|
||||
# Hence no need to check `self._user_space_ifaces`
|
||||
new_ifaces = {}
|
||||
for iface in self._kernel_ifaces.values():
|
||||
for port_name in iface.port:
|
||||
if not self._kernel_ifaces.get(port_name):
|
||||
raise NmstateValueError(
|
||||
f"Interface {iface.name} has unknown port: {port_name}"
|
||||
)
|
||||
if self._gen_conf_mode:
|
||||
logging.warning(
|
||||
f"Interface {port_name} does not exit in "
|
||||
"desire state, assuming it is ethernet"
|
||||
)
|
||||
new_ifaces[port_name] = _to_specific_iface_obj(
|
||||
{
|
||||
Interface.NAME: port_name,
|
||||
Interface.TYPE: InterfaceType.ETHERNET,
|
||||
Interface.STATE: InterfaceState.UP,
|
||||
},
|
||||
self._save_to_disk,
|
||||
)
|
||||
else:
|
||||
raise NmstateValueError(
|
||||
f"Interface {iface.name} has unknown port: "
|
||||
f"{port_name}"
|
||||
)
|
||||
self._kernel_ifaces.update(new_ifaces)
|
||||
|
||||
def _validate_unknown_parent(self):
|
||||
"""
|
||||
Check the existance of parent interface
|
||||
"""
|
||||
# All child interface should be in kernel space.
|
||||
new_ifaces = {}
|
||||
for iface in self._kernel_ifaces.values():
|
||||
if iface.parent:
|
||||
parent_iface = self._get_parent_iface(iface)
|
||||
if not parent_iface:
|
||||
raise NmstateValueError(
|
||||
f"Interface {iface.name} has unknown parent: "
|
||||
f"{iface.parent}"
|
||||
)
|
||||
if self._gen_conf_mode:
|
||||
logging.warning(
|
||||
f"Interface {iface.parent} does not exit in "
|
||||
"desire state, assuming it is ethernet"
|
||||
)
|
||||
new_ifaces[iface.parent] = _to_specific_iface_obj(
|
||||
{
|
||||
Interface.NAME: iface.parent,
|
||||
Interface.TYPE: InterfaceType.ETHERNET,
|
||||
Interface.STATE: InterfaceState.UP,
|
||||
},
|
||||
self._save_to_disk,
|
||||
)
|
||||
else:
|
||||
raise NmstateValueError(
|
||||
f"Interface {iface.name} has unknown parent: "
|
||||
f"{iface.parent}"
|
||||
)
|
||||
self._kernel_ifaces.update(new_ifaces)
|
||||
|
||||
def _remove_unknown_type_interfaces(self):
|
||||
"""
|
||||
|
@ -34,13 +34,20 @@ from .state import state_match
|
||||
|
||||
|
||||
class NetState:
|
||||
def __init__(self, desire_state, current_state=None, save_to_disk=True):
|
||||
def __init__(
|
||||
self,
|
||||
desire_state,
|
||||
current_state=None,
|
||||
save_to_disk=True,
|
||||
gen_conf_mode=False,
|
||||
):
|
||||
if current_state is None:
|
||||
current_state = {}
|
||||
self._ifaces = Ifaces(
|
||||
desire_state.get(Interface.KEY),
|
||||
current_state.get(Interface.KEY),
|
||||
save_to_disk,
|
||||
gen_conf_mode,
|
||||
)
|
||||
self._route = RouteState(
|
||||
self._ifaces,
|
||||
|
@ -21,6 +21,7 @@ import logging
|
||||
from operator import itemgetter
|
||||
|
||||
from libnmstate.error import NmstateDependencyError
|
||||
from libnmstate.error import NmstateNotSupportedError
|
||||
from libnmstate.error import NmstateValueError
|
||||
from libnmstate.ifaces.ovs import is_ovs_running
|
||||
from libnmstate.schema import DNS
|
||||
@ -61,9 +62,8 @@ from .wired import get_info as get_wired_info
|
||||
|
||||
class NetworkManagerPlugin(NmstatePlugin):
|
||||
def __init__(self):
|
||||
self._ctx = NmContext()
|
||||
self._ctx = None
|
||||
self._checkpoint = None
|
||||
self._check_version_mismatch()
|
||||
self.__applied_configs = None
|
||||
|
||||
@property
|
||||
@ -91,10 +91,13 @@ class NetworkManagerPlugin(NmstatePlugin):
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
return self._ctx.client if self._ctx else None
|
||||
return self.context.client if self.context else None
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
if not self._ctx:
|
||||
self._ctx = NmContext()
|
||||
self._check_version_mismatch()
|
||||
return self._ctx
|
||||
|
||||
@property
|
||||
@ -198,26 +201,26 @@ class NetworkManagerPlugin(NmstatePlugin):
|
||||
# Old checkpoint might timeout, hence it's legal to load
|
||||
# another one.
|
||||
self._checkpoint.clean_up()
|
||||
candidates = get_checkpoints(self._ctx.client)
|
||||
candidates = get_checkpoints(self.client)
|
||||
if checkpoint_path in candidates:
|
||||
self._checkpoint = CheckPoint(
|
||||
nm_context=self._ctx, dbuspath=checkpoint_path
|
||||
nm_context=self.context, dbuspath=checkpoint_path
|
||||
)
|
||||
else:
|
||||
raise NmstateValueError("No checkpoint specified or found")
|
||||
else:
|
||||
if not self._checkpoint:
|
||||
# Get latest one
|
||||
candidates = get_checkpoints(self._ctx.client)
|
||||
candidates = get_checkpoints(self.client)
|
||||
if candidates:
|
||||
self._checkpoint = CheckPoint(
|
||||
nm_context=self._ctx, dbuspath=candidates[0]
|
||||
nm_context=self.context, dbuspath=candidates[0]
|
||||
)
|
||||
else:
|
||||
raise NmstateValueError("No checkpoint specified or found")
|
||||
|
||||
def create_checkpoint(self, timeout=60):
|
||||
self._checkpoint = CheckPoint.create(self._ctx, timeout)
|
||||
self._checkpoint = CheckPoint.create(self.context, timeout)
|
||||
return str(self._checkpoint)
|
||||
|
||||
def rollback_checkpoint(self, checkpoint=None):
|
||||
@ -231,7 +234,7 @@ class NetworkManagerPlugin(NmstatePlugin):
|
||||
self._checkpoint = None
|
||||
|
||||
def _check_version_mismatch(self):
|
||||
nm_client_version = self._ctx.client.get_version()
|
||||
nm_client_version = self.client.get_version()
|
||||
nm_utils_version = _nm_utils_decode_version()
|
||||
|
||||
if nm_client_version is None:
|
||||
@ -248,6 +251,14 @@ class NetworkManagerPlugin(NmstatePlugin):
|
||||
nm_client_version,
|
||||
)
|
||||
|
||||
def generate_configurations(self, net_state):
|
||||
if not hasattr(NM, "keyfile_write"):
|
||||
raise NmstateNotSupportedError(
|
||||
f"Current NetworkManager version does not support generating "
|
||||
"configurations, please upgrade to 1.30 or later versoin."
|
||||
)
|
||||
return NmProfiles(None).generate_config_strings(net_state)
|
||||
|
||||
|
||||
def _remove_ovs_bridge_unsupported_entries(iface_info):
|
||||
"""
|
||||
|
@ -89,10 +89,9 @@ class NmProfile:
|
||||
ACTION_DELETE_DEVICE,
|
||||
)
|
||||
|
||||
def __init__(self, ctx, iface, save_to_disk):
|
||||
def __init__(self, ctx, iface):
|
||||
self._ctx = ctx
|
||||
self._iface = iface
|
||||
self._save_to_disk = save_to_disk
|
||||
self._nm_iface_type = None
|
||||
if self._iface.type != InterfaceType.UNKNOWN:
|
||||
self._nm_iface_type = Api2Nm.get_iface_type(self._iface.type)
|
||||
@ -105,9 +104,6 @@ class NmProfile:
|
||||
self._deactivated = False
|
||||
self._profile_deleted = False
|
||||
self._device_deleted = False
|
||||
self._import_current()
|
||||
self._gen_actions()
|
||||
self._gen_nm_sim_conn()
|
||||
|
||||
@property
|
||||
def iface(self):
|
||||
@ -119,6 +115,18 @@ class NmProfile:
|
||||
self.iface.is_changed or self.iface.is_desired
|
||||
) and not self.iface.is_ignore
|
||||
|
||||
@property
|
||||
def config_file_name(self):
|
||||
"""
|
||||
Return the profile file name used NetworkManager for key-file mode.
|
||||
"""
|
||||
if self._nm_simple_conn:
|
||||
return f"{self._nm_simple_conn.get_id()}.nmconnection"
|
||||
elif self._nm_profile:
|
||||
return f"{self._nm_profile.get_id()}.nmconnection"
|
||||
else:
|
||||
return ""
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
if self._nm_simple_conn:
|
||||
@ -136,6 +144,18 @@ class NmProfile:
|
||||
self._nm_simple_conn, self.iface.type, parent
|
||||
)
|
||||
|
||||
def to_key_file_string(self):
|
||||
nm_simple_conn = create_new_nm_simple_conn(
|
||||
self._iface, nm_profile=None
|
||||
)
|
||||
nm_simple_conn.normalize()
|
||||
# pylint: disable=no-member
|
||||
key_file = NM.keyfile_write(
|
||||
nm_simple_conn, NM.KeyfileHandlerFlags.NONE, None, None
|
||||
)
|
||||
# pylint: enable=no-member
|
||||
return key_file.to_data()[0]
|
||||
|
||||
def _gen_actions(self):
|
||||
if not self.has_pending_change:
|
||||
return
|
||||
@ -223,18 +243,17 @@ class NmProfile:
|
||||
# settings.
|
||||
self._add_action(NmProfile.ACTION_ACTIVATE_FIRST)
|
||||
|
||||
def _gen_nm_sim_conn(self):
|
||||
def prepare_config(self, save_to_disk, gen_conf_mode=False):
|
||||
if self._iface.is_absent or self._iface.is_down:
|
||||
return
|
||||
|
||||
self._check_sriov_support()
|
||||
self._check_unsupported_memory_only()
|
||||
# Don't create new profile if original desire does not ask
|
||||
# anything besides state:up and not been marked as changed.
|
||||
# We don't need to do this once we support querying on-disk
|
||||
# configure
|
||||
if (
|
||||
self._nm_profile is None
|
||||
not gen_conf_mode
|
||||
and self._nm_profile is None
|
||||
and not self._iface.is_changed
|
||||
and set(self._iface.original_dict)
|
||||
<= set([Interface.STATE, Interface.NAME, Interface.TYPE])
|
||||
@ -242,7 +261,7 @@ class NmProfile:
|
||||
cur_nm_profile = self._get_first_nm_profile()
|
||||
if (
|
||||
cur_nm_profile
|
||||
and _is_memory_only(cur_nm_profile) != self._save_to_disk
|
||||
and _is_memory_only(cur_nm_profile) != save_to_disk
|
||||
):
|
||||
self._nm_profile = cur_nm_profile
|
||||
self._nm_simple_conn = cur_nm_profile
|
||||
@ -255,7 +274,10 @@ class NmProfile:
|
||||
self._iface, self._nm_profile
|
||||
)
|
||||
|
||||
def save_config(self):
|
||||
def save_config(self, save_to_disk):
|
||||
self._check_sriov_support()
|
||||
self._check_unsupported_memory_only(save_to_disk)
|
||||
self._gen_actions()
|
||||
if not self.has_pending_change:
|
||||
return
|
||||
if self._iface.is_absent or self._iface.is_down:
|
||||
@ -267,7 +289,7 @@ class NmProfile:
|
||||
cur_nm_profile = self._get_first_nm_profile()
|
||||
if (
|
||||
cur_nm_profile
|
||||
and _is_memory_only(cur_nm_profile) != self._save_to_disk
|
||||
and _is_memory_only(cur_nm_profile) != save_to_disk
|
||||
):
|
||||
self._nm_profile = cur_nm_profile
|
||||
return
|
||||
@ -279,7 +301,7 @@ class NmProfile:
|
||||
self._iface.type,
|
||||
self._nm_simple_conn,
|
||||
self._nm_profile,
|
||||
self._save_to_disk,
|
||||
save_to_disk,
|
||||
).run()
|
||||
else:
|
||||
self._nm_profile = None
|
||||
@ -288,12 +310,12 @@ class NmProfile:
|
||||
self._iface.name,
|
||||
self._iface.type,
|
||||
self._nm_simple_conn,
|
||||
self._save_to_disk,
|
||||
save_to_disk,
|
||||
).run()
|
||||
|
||||
def _check_unsupported_memory_only(self):
|
||||
def _check_unsupported_memory_only(self, save_to_disk):
|
||||
if (
|
||||
not self._save_to_disk
|
||||
not save_to_disk
|
||||
and StrictVersion(self._ctx.client.get_version())
|
||||
< StrictVersion("1.28.0")
|
||||
and self._iface.type
|
||||
@ -376,7 +398,7 @@ class NmProfile:
|
||||
def _delete_device(self):
|
||||
if self._device_deleted:
|
||||
return
|
||||
self._import_current()
|
||||
self.import_current()
|
||||
if self._nm_dev:
|
||||
DeviceDelete(
|
||||
self._ctx, self._iface.name, self._iface.type, self._nm_dev
|
||||
@ -442,7 +464,7 @@ class NmProfile:
|
||||
else:
|
||||
time.sleep(IMPORT_NM_DEV_RETRY_INTERNAL)
|
||||
|
||||
def _import_current(self):
|
||||
def import_current(self):
|
||||
self._nm_dev = get_nm_dev(
|
||||
self._ctx, self._iface.name, self._iface.type
|
||||
)
|
||||
@ -477,7 +499,7 @@ class NmProfile:
|
||||
return
|
||||
if self._iface.is_down:
|
||||
return
|
||||
self._import_current()
|
||||
self.import_current()
|
||||
for nm_profile in self._ctx.client.get_connections():
|
||||
if is_multiconnect_profile(nm_profile):
|
||||
continue
|
||||
|
@ -42,13 +42,30 @@ class NmProfiles:
|
||||
def __init__(self, context):
|
||||
self._ctx = context
|
||||
|
||||
def generate_config_strings(self, net_state):
|
||||
_append_nm_ovs_port_iface(net_state)
|
||||
all_profiles = []
|
||||
for iface in net_state.ifaces.all_ifaces():
|
||||
if iface.is_up:
|
||||
profile = NmProfile(self._ctx, iface)
|
||||
profile.prepare_config(save_to_disk=False, gen_conf_mode=True)
|
||||
all_profiles.append(profile)
|
||||
|
||||
return [
|
||||
(profile.config_file_name, profile.to_key_file_string())
|
||||
for profile in all_profiles
|
||||
]
|
||||
|
||||
def apply_config(self, net_state, save_to_disk):
|
||||
self._prepare_state_for_profiles(net_state)
|
||||
all_profiles = [
|
||||
NmProfile(self._ctx, iface, save_to_disk)
|
||||
NmProfile(self._ctx, iface)
|
||||
for iface in net_state.ifaces.all_ifaces()
|
||||
]
|
||||
|
||||
for profile in all_profiles:
|
||||
profile.import_current()
|
||||
profile.prepare_config(save_to_disk, gen_conf_mode=False)
|
||||
_use_uuid_as_controller_and_parent(all_profiles)
|
||||
|
||||
changed_ovs_bridges_and_ifaces = {}
|
||||
@ -62,7 +79,7 @@ class NmProfiles:
|
||||
|
||||
for profile in all_profiles:
|
||||
if profile.has_pending_change:
|
||||
profile.save_config()
|
||||
profile.save_config(save_to_disk)
|
||||
self._ctx.wait_all_finish()
|
||||
|
||||
for action in NmProfile.ACTIONS:
|
||||
|
@ -28,6 +28,7 @@ import pkgutil
|
||||
from libnmstate import validator
|
||||
from libnmstate.error import NmstateError
|
||||
from libnmstate.error import NmstateValueError
|
||||
from libnmstate.error import NmstateDependencyError
|
||||
from libnmstate.schema import DNS
|
||||
from libnmstate.schema import Interface
|
||||
from libnmstate.schema import InterfaceType
|
||||
@ -37,6 +38,7 @@ from libnmstate.schema import RouteRule
|
||||
from .nispor.plugin import NisporPlugin
|
||||
from .plugin import NmstatePlugin
|
||||
from .state import merge_dict
|
||||
from .net_state import NetState
|
||||
|
||||
_INFO_TYPE_RUNNING = 1
|
||||
_INFO_TYPE_RUNNING_CONFIG = 2
|
||||
@ -173,10 +175,15 @@ def _get_interface_info_from_plugins(plugins, info_type):
|
||||
not in plugin.plugin_capabilities
|
||||
):
|
||||
continue
|
||||
if info_type == _INFO_TYPE_RUNNING_CONFIG:
|
||||
ifaces = plugin.get_running_config_interfaces()
|
||||
else:
|
||||
ifaces = plugin.get_interfaces()
|
||||
try:
|
||||
if info_type == _INFO_TYPE_RUNNING_CONFIG:
|
||||
ifaces = plugin.get_running_config_interfaces()
|
||||
else:
|
||||
ifaces = plugin.get_interfaces()
|
||||
except NmstateDependencyError as e:
|
||||
logging.warning(f"Plugin {plugin.name} error: {e}")
|
||||
continue
|
||||
|
||||
for iface in ifaces:
|
||||
iface[IFACE_PRIORITY_METADATA] = plugin.priority
|
||||
iface[IFACE_PLUGIN_SRC_METADATA] = [plugin.name]
|
||||
@ -370,3 +377,22 @@ def _get_iface_types_by_name(iface_infos, name):
|
||||
|
||||
def show_running_config_with_plugins(plugins):
|
||||
return show_with_plugins(plugins, info_type=_INFO_TYPE_RUNNING_CONFIG)
|
||||
|
||||
|
||||
def generate_configurations(desire_state):
|
||||
"""
|
||||
Return a dictionary with:
|
||||
* key: plugin name
|
||||
* vlaue: list of strings for configruations
|
||||
This function will not merge or verify desire state with current state, so
|
||||
you may run this function on different system.
|
||||
"""
|
||||
configs = {}
|
||||
net_state = NetState(desire_state, gen_conf_mode=True)
|
||||
|
||||
with plugin_context() as plugins:
|
||||
for plugin in plugins:
|
||||
config = plugin.generate_configurations(net_state)
|
||||
if config:
|
||||
configs[plugin.name] = config
|
||||
return configs
|
||||
|
@ -121,3 +121,10 @@ class NmstatePlugin(metaclass=ABCMeta):
|
||||
Retrun False when plugin can report new interface.
|
||||
"""
|
||||
return False
|
||||
|
||||
def generate_configurations(self, net_state):
|
||||
"""
|
||||
Returning a list of strings for configurations which could be save
|
||||
persistently.
|
||||
"""
|
||||
return []
|
||||
|
@ -58,6 +58,7 @@ def main():
|
||||
setup_subcommand_show(subparsers)
|
||||
setup_subcommand_version(subparsers)
|
||||
setup_subcommand_varlink(subparsers)
|
||||
setup_subcommand_gen_config(subparsers)
|
||||
parser.add_argument(
|
||||
"--version", action="store_true", help="Display nmstate version"
|
||||
)
|
||||
@ -253,6 +254,16 @@ def setup_subcommand_varlink(subparsers):
|
||||
parser_varlink.set_defaults(func=run_varlink_server)
|
||||
|
||||
|
||||
def setup_subcommand_gen_config(subparsers):
|
||||
parser_gc = subparsers.add_parser("gc", help="Generate configurations")
|
||||
parser_gc.add_argument(
|
||||
"file",
|
||||
help="File containing desired state. ",
|
||||
nargs="*",
|
||||
)
|
||||
parser_gc.set_defaults(func=run_gen_config)
|
||||
|
||||
|
||||
def version(args):
|
||||
print(libnmstate.__version__)
|
||||
|
||||
@ -354,6 +365,30 @@ def run_varlink_server(args):
|
||||
logging.exception(exception)
|
||||
|
||||
|
||||
def run_gen_config(args):
|
||||
if args.file:
|
||||
for statefile in args.file:
|
||||
if statefile == "-" and not os.path.isfile(statefile):
|
||||
statedata = sys.stdin.read()
|
||||
else:
|
||||
with open(statefile) as statefile:
|
||||
statedata = statefile.read()
|
||||
|
||||
# JSON dictionaries start with a curly brace
|
||||
if statedata[0] == "{":
|
||||
state = json.loads(statedata)
|
||||
use_yaml = False
|
||||
else:
|
||||
state = yaml.load(statedata, Loader=yaml.SafeLoader)
|
||||
use_yaml = True
|
||||
print_state(
|
||||
libnmstate.generate_configurations(state), use_yaml=use_yaml
|
||||
)
|
||||
else:
|
||||
sys.stderr.write("ERROR: No state specified\n")
|
||||
return 1
|
||||
|
||||
|
||||
def apply_state(statedata, verify_change, commit, timeout, save_to_disk):
|
||||
use_yaml = False
|
||||
# JSON dictionaries start with a curly brace
|
||||
|
@ -23,7 +23,10 @@ import pytest
|
||||
|
||||
from .testlib import assertlib
|
||||
from .testlib.examplelib import example_state
|
||||
from .testlib.examplelib import find_examples_dir
|
||||
from .testlib.examplelib import load_example
|
||||
|
||||
import libnmstate
|
||||
from libnmstate import netinfo
|
||||
from libnmstate.error import NmstateNotSupportedError
|
||||
from libnmstate.schema import DNS
|
||||
@ -228,3 +231,17 @@ def test_add_veth_and_remove():
|
||||
|
||||
assertlib.assert_absent("veth1")
|
||||
assertlib.assert_absent("veth1peer")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
nm_major_minor_version() <= 1.30,
|
||||
reason="Generating config is not supported on NetworkManager 1.30-",
|
||||
)
|
||||
def test_gen_conf_for_examples():
|
||||
example_dir = find_examples_dir()
|
||||
with os.scandir(example_dir) as example_dir_fd:
|
||||
for example_file in example_dir_fd:
|
||||
if example_file.name.endswith(".yml"):
|
||||
libnmstate.generate_configurations(
|
||||
load_example(example_file.name)
|
||||
)
|
||||
|
@ -326,7 +326,7 @@ def test_network_manager_plugin_with_daemon_stopped(stop_nm_service):
|
||||
with pytest.raises(NmstateDependencyError):
|
||||
from libnmstate.nm import NetworkManagerPlugin
|
||||
|
||||
NetworkManagerPlugin()
|
||||
NetworkManagerPlugin().context
|
||||
|
||||
state = statelib.show_only(("lo",))
|
||||
assert state[Interface.KEY][0] == LO_IFACE_INFO
|
||||
|
Loading…
x
Reference in New Issue
Block a user