diff --git a/PKG-INFO b/PKG-INFO
index 86af553..dabc333 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: nmstate
-Version: 0.4.1
+Version: 1.0.0
Summary: Declarative network manager API
Home-page: https://nmstate.github.io/
Author: Edward Haas
diff --git a/doc/nmstatectl.8 b/doc/nmstatectl.8
index 95a58c0..e5bf115 100644
--- a/doc/nmstatectl.8
+++ b/doc/nmstatectl.8
@@ -1,5 +1,5 @@
.\" Manpage for nmstatectl.
-.TH nmstatectl 8 "October 22, 2020" "0.4.1" "nmstatectl man page"
+.TH nmstatectl 8 "December 07, 2020" "1.0.0" "nmstatectl man page"
.SH NAME
nmstatectl \- A nmstate command line tool
.SH SYNOPSIS
diff --git a/examples/mac_vtap_absent.yml b/examples/mac_vtap_absent.yml
new file mode 100644
index 0000000..8b74115
--- /dev/null
+++ b/examples/mac_vtap_absent.yml
@@ -0,0 +1,5 @@
+---
+interfaces:
+ - name: macvtap0
+ type: mac-vtap
+ state: absent
diff --git a/examples/mac_vtap_create.yml b/examples/mac_vtap_create.yml
new file mode 100644
index 0000000..a2883b2
--- /dev/null
+++ b/examples/mac_vtap_create.yml
@@ -0,0 +1,9 @@
+---
+interfaces:
+ - name: macvtap0
+ type: mac-vtap
+ state: up
+ mac-vtap:
+ base-iface: eth1
+ mode: passthru
+ promiscuous: true
diff --git a/examples/team0_with_port.yml b/examples/team0_with_port.yml
index 1504716..1cd7f19 100644
--- a/examples/team0_with_port.yml
+++ b/examples/team0_with_port.yml
@@ -4,7 +4,7 @@ interfaces:
type: team
state: up
team:
- ports:
+ port:
- name: eth1
- name: eth2
runner:
diff --git a/libnmstate/VERSION b/libnmstate/VERSION
index 267577d..3eefcb9 100644
--- a/libnmstate/VERSION
+++ b/libnmstate/VERSION
@@ -1 +1 @@
-0.4.1
+1.0.0
diff --git a/libnmstate/dns.py b/libnmstate/dns.py
index 47f145b..cb5ce0b 100644
--- a/libnmstate/dns.py
+++ b/libnmstate/dns.py
@@ -166,7 +166,7 @@ class DnsState:
def _find_ifaces_with_auto_dns_false(self, ifaces):
ipv4_iface = None
ipv6_iface = None
- for iface in ifaces.values():
+ for iface in ifaces.all_kernel_ifaces.values():
if ipv4_iface and ipv6_iface:
return (ipv4_iface, ipv6_iface)
for family in (Interface.IPV4, Interface.IPV6):
@@ -180,7 +180,10 @@ class DnsState:
return (ipv4_iface, ipv6_iface)
def verify(self, cur_dns_state):
- cur_dns = DnsState(des_dns_state=None, cur_dns_state=cur_dns_state,)
+ cur_dns = DnsState(
+ des_dns_state=None,
+ cur_dns_state=cur_dns_state,
+ )
if self.config.get(DNS.SERVER, []) != cur_dns.config.get(
DNS.SERVER, []
) or self.config.get(DNS.SEARCH, []) != cur_dns.config.get(
@@ -188,7 +191,8 @@ class DnsState:
):
raise NmstateVerificationError(
format_desired_current_state_diff(
- {DNS.KEY: self.config}, {DNS.KEY: cur_dns.config},
+ {DNS.KEY: self.config},
+ {DNS.KEY: cur_dns.config},
)
)
diff --git a/libnmstate/ifaces/base_iface.py b/libnmstate/ifaces/base_iface.py
index ae54245..277c85e 100644
--- a/libnmstate/ifaces/base_iface.py
+++ b/libnmstate/ifaces/base_iface.py
@@ -124,6 +124,8 @@ class BaseIface:
DNS_METADATA = "_dns"
ROUTES_METADATA = "_routes"
ROUTE_RULES_METADATA = "_route_rules"
+ RULE_CHANGED_METADATA = "_changed"
+ ROUTE_CHANGED_METADATA = "_changed"
def __init__(self, info, save_to_disk=True):
self._origin_info = deepcopy(info)
@@ -137,6 +139,19 @@ class BaseIface:
def can_have_ip_as_port(self):
return False
+ @property
+ def is_user_space_only(self):
+ """
+ Whether this interface is user space only.
+ User space network interface means:
+ * Can have duplicate interface name against kernel network
+ interfaces
+ * Cannot be used subordinate of other kernel interfaces.
+ * Due to limtation of nmstate, currently cannot be used as
+ subordinate of other user space interfaces.
+ """
+ return False
+
def sort_port(self):
pass
@@ -233,6 +248,7 @@ class BaseIface:
if (
ip_state.is_enabled
and self.controller
+ and self.controller_type != InterfaceType.VRF
and not self.can_have_ip_as_port
):
raise NmstateValueError(
@@ -279,7 +295,10 @@ class BaseIface:
def set_controller(self, controller_iface_name, controller_type):
self._info[BaseIface.CONTROLLER_METADATA] = controller_iface_name
self._info[BaseIface.CONTROLLER_TYPE_METADATA] = controller_type
- if not self.can_have_ip_as_port:
+ if (
+ not self.can_have_ip_as_port
+ and controller_type != InterfaceType.VRF
+ ):
for family in (Interface.IPV4, Interface.IPV6):
self._info[family] = {InterfaceIP.ENABLED: False}
@@ -294,7 +313,7 @@ class BaseIface:
def gen_metadata(self, ifaces):
if self.is_controller and not self.is_absent:
for port_name in self.port:
- port_iface = ifaces[port_name]
+ port_iface = ifaces.all_kernel_ifaces[port_name]
port_iface.set_controller(self.name, self.type)
def update(self, info):
diff --git a/libnmstate/ifaces/bond.py b/libnmstate/ifaces/bond.py
index 248a74e..8f8bd6e 100644
--- a/libnmstate/ifaces/bond.py
+++ b/libnmstate/ifaces/bond.py
@@ -77,7 +77,7 @@ class BondIface(BaseIface):
def _generate_bond_mode_change_metadata(self, ifaces):
if self.is_up:
- cur_iface = ifaces.current_ifaces.get(self.name)
+ cur_iface = ifaces.get_cur_iface(self.name, self.type)
if cur_iface and self.bond_mode != cur_iface.bond_mode:
self._set_bond_mode_changed_metadata(True)
diff --git a/libnmstate/ifaces/ifaces.py b/libnmstate/ifaces/ifaces.py
index 1c82c05..52f74ed 100644
--- a/libnmstate/ifaces/ifaces.py
+++ b/libnmstate/ifaces/ifaces.py
@@ -36,6 +36,7 @@ from .ethernet import EthernetIface
from .infiniband import InfiniBandIface
from .linux_bridge import LinuxBridgeIface
from .macvlan import MacVlanIface
+from .macvtap import MacVtapIface
from .ovs import OvsBridgeIface
from .ovs import OvsInternalIface
from .team import TeamIface
@@ -44,6 +45,25 @@ from .vxlan import VxlanIface
from .vrf import VrfIface
+class _UserSpaceIfaces:
+ def __init__(self):
+ self._ifaces = {}
+
+ def set(self, iface):
+ self._ifaces[f"{iface.name}.{iface.type}"] = iface
+
+ def get(self, iface_name, iface_type):
+ return self._ifaces.get(f"{iface_name}.{iface_type}")
+
+ def remove(self, iface):
+ if self.get(iface.name, iface.type):
+ del self._ifaces[f"{iface.name}.{iface.type}"]
+
+ def __iter__(self):
+ for iface in self._ifaces.values():
+ yield iface
+
+
class Ifaces:
"""
The Ifaces class hold both desired state(optional) and current state.
@@ -63,22 +83,36 @@ class Ifaces:
def __init__(self, des_iface_infos, cur_iface_infos, save_to_disk=True):
self._save_to_disk = save_to_disk
self._des_iface_infos = des_iface_infos
- self._cur_ifaces = {}
- self._ifaces = {}
- self._ignored_iface_names = set()
+ self._cur_kernel_ifaces = {}
+ self._kernel_ifaces = {}
+ self._user_space_ifaces = _UserSpaceIfaces()
+ self._cur_user_space_ifaces = _UserSpaceIfaces()
+ self._ignored_ifaces = set()
if cur_iface_infos:
for iface_info in cur_iface_infos:
cur_iface = _to_specific_iface_obj(iface_info, save_to_disk)
- self._ifaces[cur_iface.name] = cur_iface
- self._cur_ifaces[cur_iface.name] = cur_iface
+ if cur_iface.is_user_space_only:
+ self._user_space_ifaces.set(cur_iface)
+ self._cur_user_space_ifaces.set(cur_iface)
+ else:
+ self._kernel_ifaces[cur_iface.name] = cur_iface
+ self._cur_kernel_ifaces[cur_iface.name] = cur_iface
if des_iface_infos:
for iface_info in des_iface_infos:
iface = BaseIface(iface_info, save_to_disk)
- cur_iface = self._ifaces.get(iface.name)
+ if iface.type == InterfaceType.UNKNOWN:
+ cur_ifaces = self._get_cur_ifaces(iface.name)
+ if len(cur_ifaces) > 1:
+ raise NmstateValueError(
+ f"Got multiple interface with name {iface.name}, "
+ "please specify the interface type explicitly"
+ )
+ cur_iface = self.get_iface(iface.name, iface.type)
if cur_iface and cur_iface.is_desired:
raise NmstateValueError(
- f"Duplicate interfaces names detected: {iface.name}"
+ f"Duplicate interfaces names detected: {iface.name} "
+ f"for type {cur_iface.type}"
)
if iface_info.get(Interface.TYPE) is None:
@@ -98,11 +132,16 @@ class Ifaces:
# Ignore interface with unknown type
continue
if iface.is_ignore:
- self._ignored_iface_names.add(iface.name)
+ self._ignored_ifaces.add(
+ (iface.name, iface.type, iface.is_user_space_only)
+ )
if cur_iface:
iface.merge(cur_iface)
iface.mark_as_desired()
- self._ifaces[iface.name] = iface
+ if iface.is_user_space_only:
+ self._user_space_ifaces.set(iface)
+ else:
+ self._kernel_ifaces[iface.name] = iface
self._create_virtual_port()
self._validate_unknown_port()
@@ -110,7 +149,7 @@ class Ifaces:
self._validate_infiniband_as_bridge_port()
self._validate_infiniband_as_bond_port()
self._gen_metadata()
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
iface.pre_edit_validation_and_cleanup()
self._pre_edit_validation_and_cleanup()
@@ -124,15 +163,17 @@ class Ifaces:
only in port list of OVS bridge.
"""
new_ifaces = []
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
if iface.is_up and iface.is_controller:
for port_name in iface.port:
- if port_name not in self._ifaces.keys():
+ # nmstate does not support port interface to be user
+ # space interface
+ if port_name not in self._kernel_ifaces.keys():
new_port = iface.create_virtual_port(port_name)
if new_port:
new_ifaces.append(new_port)
for iface in new_ifaces:
- self._ifaces[iface.name] = iface
+ self._kernel_ifaces[iface.name] = iface
def _pre_edit_validation_and_cleanup(self):
self._validate_over_booked_port()
@@ -140,7 +181,7 @@ class Ifaces:
self._validate_vlan_mtu()
self._handle_controller_port_list_change()
self._match_child_iface_state_with_parent()
- self._mark_orphen_as_absent()
+ self._mark_orphan_as_absent()
self._bring_port_up_if_not_in_desire()
self._validate_ovs_patch_peers()
self._remove_unknown_type_interfaces()
@@ -151,10 +192,10 @@ class Ifaces:
When port been included in controller, automactially set it as state UP
if not defiend in desire state
"""
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
if iface.is_up and iface.is_controller:
for port_name in iface.port:
- port_iface = self._ifaces[port_name]
+ port_iface = self._kernel_ifaces[port_name]
if not port_iface.is_desired and not port_iface.is_up:
port_iface.mark_as_up()
port_iface.mark_as_changed()
@@ -163,10 +204,10 @@ class Ifaces:
"""
When OVS patch peer does not exist or is down, raise an error.
"""
- for iface in self._ifaces.values():
+ for iface in self._kernel_ifaces.values():
if iface.type == InterfaceType.OVS_INTERFACE and iface.is_up:
if iface.peer:
- peer_iface = self._ifaces.get(iface.peer)
+ peer_iface = self._kernel_ifaces.get(iface.peer)
if not peer_iface or not peer_iface.is_up:
raise NmstateValueError(
f"OVS patch port peer {iface.peer} must exist and "
@@ -185,13 +226,16 @@ class Ifaces:
"""
Validate that vlan is not being created over infiniband interface
"""
- for iface in self._ifaces.values():
+ for iface in self._kernel_ifaces.values():
if (
iface.type in [InterfaceType.VLAN, InterfaceType.VXLAN]
and iface.is_up
):
- if self._ifaces[iface.parent].type == InterfaceType.INFINIBAND:
+ if (
+ self._kernel_ifaces[iface.parent].type
+ == InterfaceType.INFINIBAND
+ ):
raise NmstateValueError(
f"Interface {iface.name} of type {iface.type}"
" is not supported over base interface of "
@@ -205,14 +249,14 @@ class Ifaces:
If base MTU is not present, set same as vlan MTU
"""
- for iface in self._ifaces.values():
+ for iface in self._kernel_ifaces.values():
if (
iface.type in [InterfaceType.VLAN, InterfaceType.VXLAN]
and iface.is_up
and iface.mtu
):
- base_iface = self._ifaces.get(iface.parent)
+ base_iface = self._kernel_ifaces.get(iface.parent)
if not base_iface.mtu:
base_iface.mtu = iface.mtu
if iface.mtu > base_iface.mtu:
@@ -228,13 +272,13 @@ class Ifaces:
The IPoIB NIC has no ethernet layer, hence is no way for adding a IPoIB
NIC to linux bridge or OVS bridge
"""
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
if iface.is_desired and iface.type in (
InterfaceType.LINUX_BRIDGE,
InterfaceType.OVS_BRIDGE,
):
for port_name in iface.port:
- port_iface = self._ifaces[port_name]
+ port_iface = self._kernel_ifaces[port_name]
if port_iface.type == InterfaceType.INFINIBAND:
raise NmstateValueError(
f"The bridge {iface.name} cannot use "
@@ -247,14 +291,14 @@ class Ifaces:
The IP over InfiniBand interface is only allowed to be port of
bond in "active-backup" mode.
"""
- for iface in self._ifaces.values():
+ for iface in self._kernel_ifaces.values():
if (
iface.is_desired
and iface.type == InterfaceType.BOND
and iface.bond_mode != BondMode.ACTIVE_BACKUP
):
for port_name in iface.port:
- port_iface = self._ifaces[port_name]
+ port_iface = self._kernel_ifaces[port_name]
if port_iface.type == InterfaceType.INFINIBAND:
raise NmstateValueError(
"The IP over InfiniBand interface "
@@ -265,32 +309,32 @@ class Ifaces:
def _handle_controller_port_list_change(self):
"""
- * Mark port interface as changed if controller removed.
- * Mark port interface as changed if port list of controller changed.
- * Mark port interface as changed if port config changed when
- controller said so.
+ * Mark port interface as changed if controller removed.
+ * Mark port interface as changed if port list of controller changed.
+ * Mark port interface as changed if port config changed when
+ controller said so.
"""
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
if not iface.is_desired or not iface.is_controller:
continue
des_port = set(iface.port)
if iface.is_absent:
des_port = set()
- cur_iface = self._cur_ifaces.get(iface.name)
+ cur_iface = self.get_cur_iface(iface.name, iface.type)
cur_port = set(cur_iface.port) if cur_iface else set()
if des_port != cur_port:
changed_port = (des_port | cur_port) - (des_port & cur_port)
for iface_name in changed_port:
- self._ifaces[iface_name].mark_as_changed()
+ self._kernel_ifaces[iface_name].mark_as_changed()
if cur_iface:
for port_name in iface.config_changed_port(cur_iface):
- if port_name in self._ifaces:
- self._ifaces[port_name].mark_as_changed()
+ if port_name in self._kernel_ifaces:
+ self._kernel_ifaces[port_name].mark_as_changed()
def _validate_vrf_table_id_changes(self):
- for iface in self._ifaces.values():
+ for iface in self._kernel_ifaces.values():
if iface.is_desired and iface.type == InterfaceType.VRF:
- cur_iface = self._cur_ifaces.get(iface.name)
+ cur_iface = self._cur_kernel_ifaces.get(iface.name)
if (
cur_iface
and cur_iface.route_table_id != iface.route_table_id
@@ -308,10 +352,14 @@ class Ifaces:
* When changed/desired parent interface is marked as down or
absent, child state should sync with parent.
"""
- for iface in self._ifaces.values():
- if iface.parent and self._ifaces.get(iface.parent):
- parent_iface = self._ifaces[iface.parent]
- if parent_iface.is_desired or parent_iface.is_changed:
+ for iface in self.all_ifaces():
+ if iface.parent:
+ parent_iface = self._get_parent_iface(iface)
+ if (
+ parent_iface
+ and parent_iface.is_desired
+ or parent_iface.is_changed
+ ):
if (
Interface.STATE not in iface.original_dict
or parent_iface.is_down
@@ -320,66 +368,128 @@ class Ifaces:
iface.state = parent_iface.state
iface.mark_as_changed()
- def _mark_orphen_as_absent(self):
- for iface in self._ifaces.values():
- if iface.need_parent and (
- not iface.parent or not self._ifaces.get(iface.parent)
+ def _get_parent_iface(self, iface):
+ if not iface.parent:
+ return None
+ for cur_iface in self.all_ifaces():
+ if cur_iface.name == iface.parent and iface != cur_iface:
+ return cur_iface
+ return None
+
+ def _mark_orphan_as_absent(self):
+ for iface in self._kernel_ifaces.values():
+ if iface.need_parent and (iface.is_desired or iface.is_changed):
+ parent_iface = self._get_parent_iface(iface)
+ if parent_iface is None or parent_iface.is_absent:
+ iface.mark_as_changed()
+ iface.state = InterfaceState.ABSENT
+
+ def all_ifaces(self):
+ for iface in self._kernel_ifaces.values():
+ yield iface
+ for iface in self._user_space_ifaces:
+ yield iface
+
+ @property
+ def all_kernel_ifaces(self):
+ """
+ Return a editable dict of kernel interfaces, indexed by interface name
+ """
+ return self._kernel_ifaces
+
+ @property
+ def all_user_space_ifaces(self):
+ """
+ Return a editable user space interfaces, the object has functions:
+ * set(iface)
+ * get(iface)
+ * remove(iface)
+ * __iter__()
+ """
+ return self._user_space_ifaces
+
+ def _get_cur_ifaces(self, iface_name):
+ return [
+ iface
+ for iface in (
+ list(self._cur_kernel_ifaces.values())
+ + list(self._cur_user_space_ifaces)
+ )
+ if iface.name == iface_name
+ ]
+
+ def get_iface(self, iface_name, iface_type):
+ iface = self._kernel_ifaces.get(iface_name)
+ if iface and iface_type in (None, InterfaceType.UNKNOWN, iface.type):
+ return iface
+
+ for iface in self._user_space_ifaces:
+ if iface.name == iface_name and iface_type in (
+ None,
+ InterfaceType.UNKNOWN,
+ iface.type,
):
- iface.mark_as_changed()
- iface.state = InterfaceState.ABSENT
+ return iface
+ return None
+
+ def get_cur_iface(self, iface_name, iface_type):
- def get(self, iface_name):
- return self._ifaces.get(iface_name)
+ iface = self._cur_kernel_ifaces.get(iface_name)
+ if iface and iface_type in (None, InterfaceType.UNKNOWN, iface.type):
+ return iface
- def __getitem__(self, iface_name):
- return self._ifaces[iface_name]
+ for iface in self._cur_user_space_ifaces:
+ if iface.name == iface_name and iface_type in (
+ None,
+ InterfaceType.UNKNOWN,
+ iface.type,
+ ):
+ return iface
+ return None
+
+ def _remove_iface(self, iface_name, iface_type):
+ cur_iface = self._cur_kernel_ifaces.get(iface_name, iface_type)
+ if cur_iface:
+ self._user_space_ifaces.remove(cur_iface)
+ else:
+ cur_iface = self._kernel_ifaces.get(iface_name)
+ if (
+ iface_type
+ and iface_type != InterfaceType.UNKNOWN
+ and iface_type == cur_iface.type
+ ):
+ del self._kernel_ifaces[iface_name]
- def __setitem__(self, iface_name, iface):
- self._ifaces[iface_name] = iface
+ def add_ifaces(self, ifaces):
+ for iface in ifaces:
+ if iface.is_user_space_only:
+ self._user_space_ifaces.set(iface)
+ else:
+ self._kernel_ifaces[iface.name] = iface
def _gen_metadata(self):
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
# Generate metadata for all interface in case any of them
# been marked as changed by DNS/Route/RouteRule.
iface.gen_metadata(self)
- def keys(self):
- for iface in self._ifaces.keys():
- yield iface
-
- def values(self):
- for iface in self._ifaces.values():
- yield iface
-
- def update(self, ifaces):
- if ifaces:
- self._ifaces.update(ifaces)
-
- @property
- def current_ifaces(self):
- return self._cur_ifaces
-
@property
def state_to_edit(self):
return [
iface.to_dict()
- for iface in self._ifaces.values()
+ for iface in self.all_ifaces()
if (iface.is_changed or iface.is_desired) and not iface.is_ignore
]
- @property
- def cur_ifaces(self):
- return self._cur_ifaces
-
def _remove_unknown_interface_type_port(self):
"""
When controller containing port with unknown interface type, they
should be removed from controller port list before verifying.
"""
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
if iface.is_up and iface.is_controller and iface.port:
for port_name in iface.port:
- port_iface = self._ifaces[port_name]
+ port_iface = self._kernel_ifaces[port_name]
if port_iface.type == InterfaceType.UNKNOWN:
iface.remove_port(port_name)
@@ -390,14 +500,14 @@ class Ifaces:
save_to_disk=self._save_to_disk,
)
cur_ifaces._remove_unknown_interface_type_port()
- cur_ifaces._remove_ignore_interfaces(self._ignored_iface_names)
- self._remove_ignore_interfaces(self._ignored_iface_names)
- for iface in self._ifaces.values():
+ cur_ifaces._remove_ignore_interfaces(self._ignored_ifaces)
+ self._remove_ignore_interfaces(self._ignored_ifaces)
+ for iface in self.all_ifaces():
if iface.is_desired:
if iface.is_virtual and iface.original_dict.get(
Interface.STATE
) in (InterfaceState.DOWN, InterfaceState.ABSENT):
- cur_iface = cur_ifaces.get(iface.name)
+ cur_iface = cur_ifaces.get_iface(iface.name, iface.type)
if cur_iface:
raise NmstateVerificationError(
format_desired_current_state_diff(
@@ -406,7 +516,7 @@ class Ifaces:
)
)
elif iface.is_up or (iface.is_down and not iface.is_virtual):
- cur_iface = cur_ifaces.get(iface.name)
+ cur_iface = cur_ifaces.get_iface(iface.name, iface.type)
if not cur_iface:
raise NmstateVerificationError(
format_desired_current_state_diff(
@@ -447,56 +557,75 @@ class Ifaces:
def gen_dns_metadata(self, dns_state, route_state):
iface_metadata = dns_state.gen_metadata(self, route_state)
for iface_name, dns_metadata in iface_metadata.items():
- self._ifaces[iface_name].store_dns_metadata(dns_metadata)
+ self._kernel_ifaces[iface_name].store_dns_metadata(dns_metadata)
if dns_state.config_changed:
- self._ifaces[iface_name].mark_as_changed()
+ self._kernel_ifaces[iface_name].mark_as_changed()
def gen_route_metadata(self, route_state):
iface_metadata = route_state.gen_metadata(self)
for iface_name, route_metadata in iface_metadata.items():
- self._ifaces[iface_name].store_route_metadata(route_metadata)
+ route_state = route_metadata.pop(
+ BaseIface.ROUTE_CHANGED_METADATA, None
+ )
+ if route_state:
+ self._kernel_ifaces[iface_name].mark_as_changed()
+
+ self._kernel_ifaces[iface_name].store_route_metadata(
+ route_metadata
+ )
def gen_route_rule_metadata(self, route_rule_state, route_state):
iface_metadata = route_rule_state.gen_metadata(
- route_state, self._ifaces
+ route_state, self._kernel_ifaces
)
for iface_name, route_rule_metadata in iface_metadata.items():
- self._ifaces[iface_name].store_route_rule_metadata(
+ rule_state = route_rule_metadata.pop(
+ BaseIface.RULE_CHANGED_METADATA, None
+ )
+ if rule_state:
+ self._kernel_ifaces[iface_name].mark_as_changed()
+
+ self._kernel_ifaces[iface_name].store_route_rule_metadata(
route_rule_metadata
)
- if route_rule_state.config_changed:
- self._ifaces[iface_name].mark_as_changed()
def _validate_unknown_port(self):
"""
Check the existance of port interface
"""
- for iface in self._ifaces.values():
+ # 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`
+ for iface in self._kernel_ifaces.values():
for port_name in iface.port:
- if not self._ifaces.get(port_name):
+ if not self._kernel_ifaces.get(port_name):
raise NmstateValueError(
- f"Interface {iface.name} has unknown port: "
- f"{port_name}"
+ f"Interface {iface.name} has unknown port: {port_name}"
)
def _validate_unknown_parent(self):
"""
Check the existance of parent interface
"""
- for iface in self._ifaces.values():
- if iface.parent and not self._ifaces.get(iface.parent):
- raise NmstateValueError(
- f"Interface {iface.name} has unknown parent: "
- f"{iface.parent}"
- )
+ # All child interface should be in kernel space.
+ 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}"
+ )
def _remove_unknown_type_interfaces(self):
"""
Remove unknown type interfaces that are set as up.
"""
- for iface in list(self._ifaces.values()):
+ # All the user space interface already has interface type defined.
+ # Hence no need to check `self._user_space_ifaces`
+ for iface in list(self._kernel_ifaces.values()):
if iface.type == InterfaceType.UNKNOWN and iface.is_up:
- self._ifaces.pop(iface.name, None)
+ self._kernel_ifaces.pop(iface.name, None)
logging.debug(
f"Interface {iface.name} is type {iface.type} and "
"will be ignored during the activation"
@@ -507,11 +636,15 @@ class Ifaces:
Check whether any port is used by more than one controller
"""
port_controller_map = {}
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
for port_name in iface.port:
cur_controller = port_controller_map.get(port_name)
if cur_controller:
- cur_controller_iface = self._ifaces.get(cur_controller)
+ # Only kernel requires each interface to have
+ # one controller interface at most.
+ cur_controller_iface = self._kernel_ifaces.get(
+ cur_controller
+ )
if (
cur_controller_iface
and not cur_controller_iface.is_absent
@@ -523,15 +656,23 @@ class Ifaces:
else:
port_controller_map[port_name] = iface.name
- def _remove_ignore_interfaces(self, ignored_iface_names):
+ def _remove_ignore_interfaces(self, ignored_ifaces):
+ for iface_name, iface_type, _ in ignored_ifaces:
+ self._remove_iface(iface_name, iface_type)
+
+ # Only kernel interface can be used as port
+ ignored_kernel_iface_names = set(
+ iface_name
+ for iface_name, _, is_user_space_only in ignored_ifaces
+ if not is_user_space_only
+ )
+
# Remove ignored port
- for iface in self._ifaces.values():
+ for iface in self.all_ifaces():
if iface.is_up and iface.is_controller and iface.port:
for port_name in iface.port:
- if port_name in ignored_iface_names:
+ if port_name in ignored_kernel_iface_names:
iface.remove_port(port_name)
- for ignored_iface_name in ignored_iface_names:
- self._ifaces.pop(ignored_iface_name, None)
def _to_specific_iface_obj(info, save_to_disk):
@@ -560,5 +701,7 @@ def _to_specific_iface_obj(info, save_to_disk):
return InfiniBandIface(info, save_to_disk)
elif iface_type == InterfaceType.MAC_VLAN:
return MacVlanIface(info, save_to_disk)
+ elif iface_type == InterfaceType.MAC_VTAP:
+ return MacVtapIface(info, save_to_disk)
else:
return BaseIface(info, save_to_disk)
diff --git a/libnmstate/ifaces/infiniband.py b/libnmstate/ifaces/infiniband.py
index 0e2623f..5292094 100644
--- a/libnmstate/ifaces/infiniband.py
+++ b/libnmstate/ifaces/infiniband.py
@@ -75,9 +75,9 @@ class InfiniBandIface(BaseIface):
def _cannonicalize_pkey(iface_info):
"""
- * Set as 0xffff if pkey not defined
- * Convert pkey string to integer
- * Raise NmstateValueError when out of range(16 bites)
+ * Set as 0xffff if pkey not defined
+ * Convert pkey string to integer
+ * Raise NmstateValueError when out of range(16 bites)
"""
iface_name = iface_info[Interface.NAME]
ib_config = iface_info.get(InfiniBand.CONFIG_SUBTREE, {})
diff --git a/libnmstate/ifaces/linux_bridge.py b/libnmstate/ifaces/linux_bridge.py
index 3f4af89..4ae8dad 100644
--- a/libnmstate/ifaces/linux_bridge.py
+++ b/libnmstate/ifaces/linux_bridge.py
@@ -124,9 +124,9 @@ class LinuxBridgeIface(BridgeIface):
super().gen_metadata(ifaces)
if not self.is_absent:
for port_config in self.port_configs:
- ifaces[port_config[LinuxBridge.Port.NAME]].update(
- {BridgeIface.BRPORT_OPTIONS_METADATA: port_config}
- )
+ ifaces.all_kernel_ifaces[
+ port_config[LinuxBridge.Port.NAME]
+ ].update({BridgeIface.BRPORT_OPTIONS_METADATA: port_config})
def remove_port(self, port_name):
if self._bridge_config:
diff --git a/libnmstate/ifaces/macvlan.py b/libnmstate/ifaces/macvlan.py
index 06bccaf..33adbe0 100644
--- a/libnmstate/ifaces/macvlan.py
+++ b/libnmstate/ifaces/macvlan.py
@@ -26,14 +26,14 @@ from .base_iface import BaseIface
class MacVlanIface(BaseIface):
@property
def parent(self):
- return self._macvlan_config.get(MacVlan.BASE_IFACE)
+ return self.config_subtree.get(MacVlan.BASE_IFACE)
@property
def need_parent(self):
return True
@property
- def _macvlan_config(self):
+ def config_subtree(self):
return self.raw.get(MacVlan.CONFIG_SUBTREE, {})
@property
@@ -50,15 +50,15 @@ class MacVlanIface(BaseIface):
super().pre_edit_validation_and_cleanup()
def _validate_mode(self):
- if self._macvlan_config.get(
+ if self.config_subtree.get(
MacVlan.MODE
- ) != MacVlan.Mode.PASSTHRU and not self._macvlan_config.get(
+ ) != MacVlan.Mode.PASSTHRU and not self.config_subtree.get(
MacVlan.PROMISCUOUS
):
raise NmstateValueError(
"Disable promiscuous is only allowed on passthru mode"
)
- if self._macvlan_config.get(MacVlan.MODE) == MacVlan.Mode.UNKNOWN:
+ if self.config_subtree.get(MacVlan.MODE) == MacVlan.Mode.UNKNOWN:
raise NmstateValueError(
"Mode unknown is not supported when appying the state"
)
@@ -66,8 +66,8 @@ class MacVlanIface(BaseIface):
def _validate_mandatory_properties(self):
if self.is_up:
for prop in (MacVlan.MODE, MacVlan.BASE_IFACE):
- if prop not in self._macvlan_config:
+ if prop not in self.config_subtree:
raise NmstateValueError(
- f"MacVlan tunnel {self.name} has missing mandatory "
- f"property: {prop}"
+ f"{self.type} tunnel {self.name} has missing mandatory"
+ f" property: {prop}"
)
diff --git a/libnmstate/ifaces/macvtap.py b/libnmstate/ifaces/macvtap.py
new file mode 100644
index 0000000..606aa19
--- /dev/null
+++ b/libnmstate/ifaces/macvtap.py
@@ -0,0 +1,32 @@
+#
+# 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 .
+#
+
+from libnmstate.schema import MacVtap
+
+from .macvlan import MacVlanIface
+
+
+class MacVtapIface(MacVlanIface):
+ @property
+ def parent(self):
+ return self.config_subtree.get(MacVtap.BASE_IFACE)
+
+ @property
+ def config_subtree(self):
+ return self.raw.get(MacVtap.CONFIG_SUBTREE, {})
diff --git a/libnmstate/ifaces/ovs.py b/libnmstate/ifaces/ovs.py
index 4fbf5fc..d28de61 100644
--- a/libnmstate/ifaces/ovs.py
+++ b/libnmstate/ifaces/ovs.py
@@ -45,6 +45,10 @@ class OvsBridgeIface(BridgeIface):
self._replace_deprecated_terms()
@property
+ def is_user_space_only(self):
+ return True
+
+ @property
def _has_bond_port(self):
for port_config in self.port_configs:
if port_config.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE):
@@ -82,16 +86,16 @@ class OvsBridgeIface(BridgeIface):
return port
def gen_metadata(self, ifaces):
- for port_name in self.port:
- port_iface = ifaces[port_name]
- port_config = _lookup_ovs_port_by_interface(
- self.port_configs, port_iface.name
+ for ovs_iface_name in self.port:
+ ovs_iface = ifaces.all_kernel_ifaces[ovs_iface_name]
+ ovs_iface_config = _lookup_ovs_iface_config(
+ self.port_configs, ovs_iface_name
)
- port_iface.update(
- {BridgeIface.BRPORT_OPTIONS_METADATA: port_config}
+ ovs_iface.update(
+ {BridgeIface.BRPORT_OPTIONS_METADATA: ovs_iface_config}
)
- if port_iface.type == InterfaceType.OVS_INTERFACE:
- port_iface.parent = self.name
+ if ovs_iface.type == InterfaceType.OVS_INTERFACE:
+ ovs_iface.parent = self.name
super().gen_metadata(ifaces)
def create_virtual_port(self, port_name):
@@ -167,13 +171,13 @@ class OvsBridgeIface(BridgeIface):
)
-def _lookup_ovs_port_by_interface(ports, port_name):
- for port in ports:
- lag_state = port.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
- if lag_state and _is_ovs_lag_port(lag_state, port_name):
- return port
- elif port[OVSBridge.Port.NAME] == port_name:
- return port
+def _lookup_ovs_iface_config(bridge_port_configs, ovs_iface_name):
+ for port_config in bridge_port_configs:
+ lag_state = port_config.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE)
+ if lag_state and _is_ovs_lag_port(lag_state, ovs_iface_name):
+ return port_config
+ elif port_config[OVSBridge.Port.NAME] == ovs_iface_name:
+ return port_config
return {}
@@ -282,3 +286,13 @@ def _convert_external_ids_values_to_string(iface_info):
def is_ovs_lag_port(port_state):
return port_state.get(OVSBridge.Port.LINK_AGGREGATION_SUBTREE) is not None
+
+
+class OvsPortIface(BaseIface):
+ def __init__(self, info, save_to_disk=True):
+ super().__init__(info, save_to_disk)
+ self._parent = None
+
+ @property
+ def is_user_space_only(self):
+ return True
diff --git a/libnmstate/ifaces/team.py b/libnmstate/ifaces/team.py
index e572269..e30eab3 100644
--- a/libnmstate/ifaces/team.py
+++ b/libnmstate/ifaces/team.py
@@ -18,13 +18,21 @@
#
from operator import itemgetter
+import warnings
from libnmstate.schema import Team
from .base_iface import BaseIface
+DEPRECATED_PORTS = "ports"
+
+
class TeamIface(BaseIface):
+ def __init__(self, info, save_to_disk=True):
+ super().__init__(info, save_to_disk)
+ self._replace_deprecated_terms()
+
@property
def port(self):
ports = self.raw.get(Team.CONFIG_SUBTREE, {}).get(
@@ -53,3 +61,9 @@ class TeamIface(BaseIface):
s for s in port_config if s[Team.Port.NAME] != port_name
]
self.sort_port()
+
+ def _replace_deprecated_terms(self):
+ team_cfg = self.raw.get(Team.CONFIG_SUBTREE)
+ if team_cfg and team_cfg.get(DEPRECATED_PORTS):
+ team_cfg[Team.PORT_SUBTREE] = team_cfg.pop(DEPRECATED_PORTS)
+ warnings.warn("Using 'ports' is deprecated, use 'port' instead.")
diff --git a/libnmstate/net_state.py b/libnmstate/net_state.py
index 2d80b53..4936e0b 100644
--- a/libnmstate/net_state.py
+++ b/libnmstate/net_state.py
@@ -45,7 +45,8 @@ class NetState:
current_state.get(Route.KEY),
)
self._dns = DnsState(
- desire_state.get(DNS.KEY), current_state.get(DNS.KEY),
+ desire_state.get(DNS.KEY),
+ current_state.get(DNS.KEY),
)
self._route_rule = RouteRuleState(
self._route,
diff --git a/libnmstate/nispor/base_iface.py b/libnmstate/nispor/base_iface.py
index d5f5305..55abb7f 100644
--- a/libnmstate/nispor/base_iface.py
+++ b/libnmstate/nispor/base_iface.py
@@ -40,10 +40,9 @@ class NisporPluginBaseIface:
@property
def mac(self):
- mac = self._np_iface.mac_address
- if not mac:
- mac = DEFAULT_MAC_ADDRESS
- return mac
+ if self._np_iface.mac_address:
+ return self._np_iface.mac_address.upper()
+ return DEFAULT_MAC_ADDRESS
@property
def mtu(self):
@@ -57,9 +56,9 @@ class NisporPluginBaseIface:
def state(self):
np_state = self._np_iface.state
np_flags = self._np_iface.flags
- if np_state == "Up" or "Up" in np_flags or "Running" in np_flags:
+ if np_state == "up" or "up" in np_flags or "running" in np_flags:
return InterfaceState.UP
- elif np_state == "Down":
+ elif np_state == "down":
return InterfaceState.DOWN
else:
logging.debug(
diff --git a/libnmstate/nispor/bridge.py b/libnmstate/nispor/bridge.py
index 1da5830..5f57f9b 100644
--- a/libnmstate/nispor/bridge.py
+++ b/libnmstate/nispor/bridge.py
@@ -38,10 +38,10 @@ NISPOR_USER_HZ_KEYS = [
NISPOR_MULTICAST_ROUTERS_INT_MAP = {
- "Disabled": 0,
- "TempQuery": 1,
- "Perm": 2,
- "Temp": 3,
+ "disabled": 0,
+ "temp_query": 1,
+ "perm": 2,
+ "temp": 3,
}
LSM_BRIDGE_OPTIONS_2_NISPOR = {
@@ -127,7 +127,9 @@ def _nispor_value_to_lsm(np_name, np_value):
if np_name == "multicast_router":
value = NISPOR_MULTICAST_ROUTERS_INT_MAP.get(np_value)
elif np_name == "stp_state":
- value = np_value in ("KernelStp", "UserStp")
+ value = np_value in ("kernel_stp", "user_stp")
+ elif np_name == "group_addr":
+ value = value.upper()
return value
diff --git a/libnmstate/nispor/macvtap.py b/libnmstate/nispor/macvtap.py
new file mode 100644
index 0000000..b214961
--- /dev/null
+++ b/libnmstate/nispor/macvtap.py
@@ -0,0 +1,53 @@
+#
+# 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 .
+#
+
+from libnmstate.schema import InterfaceType
+from libnmstate.schema import MacVtap
+
+from .macvlan import NisporPluginMacVlanIface
+
+
+MACVTAP_MODES = {
+ "unknown": MacVtap.Mode.UNKNOWN,
+ "vepa": MacVtap.Mode.VEPA,
+ "bridge": MacVtap.Mode.BRIDGE,
+ "private": MacVtap.Mode.PRIVATE,
+ "passthru": MacVtap.Mode.PASSTHRU,
+ "source": MacVtap.Mode.SOURCE,
+}
+
+
+class NisporPluginMacVtapIface(NisporPluginMacVlanIface):
+ @property
+ def type(self):
+ return InterfaceType.MAC_VTAP
+
+ def to_dict(self):
+ info = super().to_dict()
+ info[MacVtap.CONFIG_SUBTREE] = {
+ MacVtap.BASE_IFACE: self._np_iface.base_iface,
+ MacVtap.MODE: MACVTAP_MODES.get(
+ self._np_iface.mode, MacVtap.Mode.UNKNOWN
+ ),
+ MacVtap.PROMISCUOUS: self._flag_to_promiscuous(
+ self._np_iface.mac_vlan_flags
+ ),
+ }
+
+ return info
diff --git a/libnmstate/nispor/plugin.py b/libnmstate/nispor/plugin.py
index 3803deb..f36a1c4 100644
--- a/libnmstate/nispor/plugin.py
+++ b/libnmstate/nispor/plugin.py
@@ -21,6 +21,7 @@ from nispor import NisporNetState
from libnmstate.plugin import NmstatePlugin
from libnmstate.schema import Route
+from libnmstate.schema import RouteRule
from .base_iface import NisporPluginBaseIface
from .bond import NisporPluginBondIface
@@ -28,9 +29,11 @@ from .bridge import NisporPluginBridgeIface
from .dummy import NisporPluginDummyIface
from .ethernet import NisporPluginEthernetIface
from .macvlan import NisporPluginMacVlanIface
+from .macvtap import NisporPluginMacVtapIface
from .vlan import NisporPluginVlanIface
from .vxlan import NisporPluginVxlanIface
from .route import nispor_route_state_to_nmstate
+from .route_rule import nispor_route_rule_state_to_nmstate
from .vrf import NisporPluginVrfIface
from .ovs import NisporPluginOvsInternalIface
@@ -45,6 +48,7 @@ class NisporPlugin(NmstatePlugin):
return [
NmstatePlugin.PLUGIN_CAPABILITY_IFACE,
NmstatePlugin.PLUGIN_CAPABILITY_ROUTE,
+ NmstatePlugin.PLUGIN_CAPABILITY_ROUTE_RULE,
]
@property
@@ -59,21 +63,23 @@ class NisporPlugin(NmstatePlugin):
ifaces = []
for np_iface in np_state.ifaces.values():
iface_type = np_iface.type
- if iface_type == "Dummy":
+ if iface_type == "dummy":
ifaces.append(NisporPluginDummyIface(np_iface).to_dict())
- elif iface_type == "Veth":
+ elif iface_type == "veth":
ifaces.append(NisporPluginEthernetIface(np_iface).to_dict())
- elif iface_type == "Ethernet":
+ elif iface_type == "ethernet":
ifaces.append(NisporPluginEthernetIface(np_iface).to_dict())
- elif iface_type == "Bond":
+ elif iface_type == "bond":
ifaces.append(NisporPluginBondIface(np_iface).to_dict())
- elif iface_type == "Vlan":
+ elif iface_type == "vlan":
ifaces.append(NisporPluginVlanIface(np_iface).to_dict())
- elif iface_type == "Vxlan":
+ elif iface_type == "vxlan":
ifaces.append(NisporPluginVxlanIface(np_iface).to_dict())
- elif iface_type == "MacVlan":
+ elif iface_type == "mac_vlan":
ifaces.append(NisporPluginMacVlanIface(np_iface).to_dict())
- elif iface_type == "Bridge":
+ elif iface_type == "mac_vtap":
+ ifaces.append(NisporPluginMacVtapIface(np_iface).to_dict())
+ elif iface_type == "bridge":
np_ports = []
for port_name in np_iface.ports:
if port_name in np_state.ifaces.keys():
@@ -81,9 +87,9 @@ class NisporPlugin(NmstatePlugin):
ifaces.append(
NisporPluginBridgeIface(np_iface, np_ports).to_dict()
)
- elif iface_type == "Vrf":
+ elif iface_type == "vrf":
ifaces.append(NisporPluginVrfIface(np_iface).to_dict())
- elif iface_type == "OpenvSwitch":
+ elif iface_type == "openv_switch":
ifaces.append(NisporPluginOvsInternalIface(np_iface).to_dict())
else:
ifaces.append(NisporPluginBaseIface(np_iface).to_dict())
@@ -92,3 +98,11 @@ class NisporPlugin(NmstatePlugin):
def get_routes(self):
np_state = NisporNetState.retrieve()
return {Route.RUNNING: nispor_route_state_to_nmstate(np_state.routes)}
+
+ def get_route_rules(self):
+ np_state = NisporNetState.retrieve()
+ return {
+ RouteRule.CONFIG: nispor_route_rule_state_to_nmstate(
+ np_state.route_rules
+ )
+ }
diff --git a/libnmstate/nispor/route.py b/libnmstate/nispor/route.py
index 852a17f..f4a445d 100644
--- a/libnmstate/nispor/route.py
+++ b/libnmstate/nispor/route.py
@@ -28,7 +28,7 @@ def nispor_route_state_to_nmstate(np_routes):
return [
_nispor_route_to_nmstate(rt)
for rt in np_routes
- if rt.scope == "Universe"
+ if rt.scope == "universe"
]
@@ -38,7 +38,7 @@ def _nispor_route_to_nmstate(np_rt):
elif np_rt.gateway:
destination = (
IPV6_DEFAULT_GATEWAY_DESTINATION
- if np_rt.address_family == "IPv6"
+ if np_rt.address_family == "ipv6"
else IPV4_DEFAULT_GATEWAY_DESTINATION
)
else:
diff --git a/libnmstate/nispor/route_rule.py b/libnmstate/nispor/route_rule.py
new file mode 100644
index 0000000..057299c
--- /dev/null
+++ b/libnmstate/nispor/route_rule.py
@@ -0,0 +1,45 @@
+#
+# 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 .
+#
+
+from libnmstate.schema import RouteRule
+
+
+NISPOR_RULE_ACTION_TABLE = "table"
+
+
+def nispor_route_rule_state_to_nmstate(np_rules):
+ return [
+ _nispor_route_rule_to_nmstate(rl)
+ for rl in np_rules
+ if (rl.src or rl.dst) and rl.action == NISPOR_RULE_ACTION_TABLE
+ ]
+
+
+def _nispor_route_rule_to_nmstate(np_rl):
+ rule = {
+ RouteRule.ROUTE_TABLE: np_rl.table,
+ RouteRule.PRIORITY: np_rl.priority
+ if np_rl.priority
+ else RouteRule.USE_DEFAULT_PRIORITY,
+ }
+ if np_rl.src:
+ rule[RouteRule.IP_FROM] = np_rl.src
+ if np_rl.dst:
+ rule[RouteRule.IP_TO] = np_rl.dst
+ return rule
diff --git a/libnmstate/nm/__init__.py b/libnmstate/nm/__init__.py
index e9bb3cf..5f42e90 100644
--- a/libnmstate/nm/__init__.py
+++ b/libnmstate/nm/__init__.py
@@ -17,35 +17,6 @@
# along with this program. If not, see .
#
-from . import bond
-from . import bridge
-from . import checkpoint
-from . import connection
-from . import device
-from . import dns
-from . import ipv4
-from . import ipv6
-from . import ovs
-from . import profile
-from . import translator
-from . import user
-from . import vlan
-from . import wired
from .plugin import NetworkManagerPlugin
-
-bond
-bridge
-checkpoint
-connection
-device
-dns
-ipv4
-ipv6
-ovs
-profile
-translator
-user
-vlan
-wired
NetworkManagerPlugin
diff --git a/libnmstate/nm/active_connection.py b/libnmstate/nm/active_connection.py
index b914258..d72d08d 100644
--- a/libnmstate/nm/active_connection.py
+++ b/libnmstate/nm/active_connection.py
@@ -20,93 +20,334 @@
import logging
from libnmstate.error import NmstateLibnmError
+from libnmstate.error import NmstateInternalError
from .common import GLib
-from .common import GObject
from .common import NM
+from .device import get_nm_dev
+from .device import get_iface_type
+from .device import mark_device_as_managed
+from .ipv4 import is_dynamic as is_ipv4_dynamic
+from .ipv6 import is_dynamic as is_ipv6_dynamic
NM_AC_STATE_CHANGED_SIGNAL = "state-changed"
-class ActivationError(Exception):
- pass
+def is_activated(nm_ac, nm_dev):
+ if not (nm_ac and nm_dev):
+ return False
+ state = nm_ac.get_state()
+ if state == NM.ActiveConnectionState.ACTIVATED:
+ return True
+ elif state == NM.ActiveConnectionState.ACTIVATING:
+ ac_state_flags = nm_ac.get_state_flags()
+ nm_flags = NM.ActivationStateFlags
+ ip4_is_dynamic = is_ipv4_dynamic(nm_ac)
+ ip6_is_dynamic = is_ipv6_dynamic(nm_ac)
+ if (
+ ac_state_flags & nm_flags.IS_MASTER
+ or (ip4_is_dynamic and ac_state_flags & nm_flags.IP6_READY)
+ or (ip6_is_dynamic and ac_state_flags & nm_flags.IP4_READY)
+ or (ip4_is_dynamic and ip6_is_dynamic)
+ ):
+ # For interface meet any condition below will be
+ # treated as activated when reach IP_CONFIG state:
+ # * Is controller device.
+ # * DHCPv4 enabled with IP6_READY flag.
+ # * DHCPv6/Autoconf with IP4_READY flag.
+ # * DHCPv4 enabled with DHCPv6/Autoconf enabled.
+ return (
+ NM.DeviceState.IP_CONFIG
+ <= nm_dev.get_state()
+ <= NM.DeviceState.ACTIVATED
+ )
-class ActiveConnection:
- def __init__(self, context=None, nm_ac_con=None):
- self._ctx = context
- self._act_con = nm_ac_con
+ return False
- nmdevs = None
- if nm_ac_con:
- nmdevs = nm_ac_con.get_devices()
- self._nmdev = nmdevs[0] if nmdevs else None
- def import_by_device(self, nmdev=None):
- assert self._act_con is None
+def is_activating(nm_ac, nm_dev):
+ if not nm_ac or not nm_dev:
+ return True
+ if nm_dev.get_state_reason() == NM.DeviceStateReason.NEW_ACTIVATION:
+ return True
- if nmdev:
- self._nmdev = nmdev
- if self._nmdev:
- self._act_con = self._nmdev.get_active_connection()
+ return (
+ nm_ac.get_state() == NM.ActiveConnectionState.ACTIVATING
+ ) and not is_activated(nm_ac, nm_dev)
- def deactivate(self):
- """
- Deactivating the current active connection,
- The profile itself is not removed.
- For software devices, deactivation removes the devices from the kernel.
- """
- act_connection = self._nmdev.get_active_connection()
- if (
- not act_connection
- or act_connection.props.state
- == NM.ActiveConnectionState.DEACTIVATED
- ):
+class ProfileActivation:
+ def __init__(self, ctx, iface_name, iface_type, nm_profile, nm_dev):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_ac = None
+ self._nm_dev = nm_dev
+ self._nm_profile = nm_profile
+ self._ac_handlers = set()
+ self._dev_handlers = set()
+ self._action = None
+
+ def run(self):
+ specific_object = None
+ self._action = (
+ f"Activate profile uuid:{self._nm_profile.get_uuid()} "
+ f"iface:{self._iface_name} type: {self._iface_type}"
+ )
+ if self._nm_dev:
+ # Workaround of https://bugzilla.redhat.com/1880420
+ mark_device_as_managed(self._ctx, self._nm_dev)
+
+ user_data = None
+ self._ctx.register_async(self._action)
+ self._ctx.client.activate_connection_async(
+ self._nm_profile,
+ self._nm_dev,
+ specific_object,
+ self._ctx.cancellable,
+ self._activate_profile_callback,
+ user_data,
+ )
+
+ @staticmethod
+ def wait(ctx, nm_ac, nm_dev):
+ activation = ProfileActivation(
+ ctx,
+ nm_dev.get_iface(),
+ get_iface_type(nm_dev),
+ None,
+ nm_dev,
+ )
+ activation._nm_ac = nm_ac
+ activation._action = (
+ f"Waiting activation of {activation._iface_name} "
+ f"{activation._iface_type}"
+ )
+ ctx.register_async(activation._action)
+ activation._wait_profile_activation()
+
+ def _activate_profile_callback(self, nm_client, result, _user_data):
+ nm_ac = None
+ if self._ctx.is_cancelled():
+ self._activation_clean_up()
return
+ try:
+ nm_ac = nm_client.activate_connection_finish(result)
+ except Exception as e:
+ self._ctx.fail(
+ NmstateLibnmError(f"{self._action} failed: error={e}")
+ )
+
+ if nm_ac is None:
+ self._ctx.fail(
+ NmstateLibnmError(
+ f"{self._action} failed: "
+ "error='None return from activate_connection_finish()'"
+ )
+ )
+ else:
+ logging.debug(
+ f"Connection activation initiated: iface={self._iface_name} "
+ f"type={self._iface_type} con-state={nm_ac.get_state()}"
+ )
+ self._nm_ac = nm_ac
+ self._nm_dev = get_nm_dev(
+ self._ctx, self._iface_name, self._iface_type
+ )
+ self._wait_profile_activation()
- if self._act_con != act_connection:
- raise NmstateLibnmError(
- "When deactivating active connection, the newly get "
- f"NM.ActiveConnection {act_connection}"
- f"is different from original request: {self._act_con}"
+ def _wait_profile_activation(self):
+ if is_activated(self._nm_ac, self._nm_dev):
+ logging.debug(
+ "Connection activation succeeded: "
+ f"iface={self._iface_name}, type={self._iface_type}, "
+ f"con_state={self._nm_ac.get_state()}, "
+ f"dev_state={self._nm_dev.get_state()}, "
+ f"state_flags={self._nm_ac.get_state_flags()}"
+ )
+ self._activation_clean_up()
+ self._ctx.finish_async(self._action)
+ elif is_activating(self._nm_ac, self._nm_dev):
+ if self._nm_ac:
+ self._wait_nm_ac_activation()
+ if self._nm_dev:
+ self._wait_nm_dev_activation()
+ if not self._nm_ac and not self._nm_dev:
+ self._ctx.fail(
+ NmstateInternalError(
+ f"{self._action} failed: no nm_ac or nm_dev"
+ )
+ )
+ else:
+ if self._nm_dev:
+ error_msg = (
+ f"Connection {self._nm_profile.get_uuid()} failed: "
+ f"state={self._nm_ac.get_state()} "
+ f"reason={self._nm_ac.get_state_reason()} "
+ f"dev_state={self._nm_dev.get_state()} "
+ f"dev_reason={self._nm_dev.get_state_reason()}"
+ )
+ else:
+ error_msg = (
+ f"Connection {self._nm_profile.get_uuid()} failed: "
+ f"state={self._nm_ac.get_state()} "
+ f"reason={self._nm_ac.get_state_reason()} dev=None"
+ )
+ self._activation_clean_up()
+ logging.error(error_msg)
+ self._ctx.fail(
+ NmstateLibnmError(f"{self._action} failed: {error_msg}")
)
- action = f"Deactivate profile: {self.devname}"
+ def _activation_clean_up(self):
+ self._remove_ac_handlers()
+ self._remove_dev_handlers()
+
+ def _remove_ac_handlers(self):
+ for handler_id in self._ac_handlers:
+ self._nm_ac.handler_disconnect(handler_id)
+ self._ac_handlers = set()
+
+ def _remove_dev_handlers(self):
+ for handler_id in self._dev_handlers:
+ self._nm_dev.handler_disconnect(handler_id)
+ self._dev_handlers = set()
+
+ def _wait_nm_ac_activation(self):
+ user_data = None
+ self._ac_handlers.add(
+ self._nm_ac.connect(
+ NM_AC_STATE_CHANGED_SIGNAL,
+ self._ac_state_change_callback,
+ user_data,
+ )
+ )
+ self._ac_handlers.add(
+ self._nm_ac.connect(
+ "notify::state-flags",
+ self._ac_state_flags_change_callback,
+ user_data,
+ )
+ )
+
+ def _ac_state_change_callback(self, _nm_ac, _state, _reason, _user_data):
+ if self._ctx.is_cancelled():
+ self._activation_clean_up()
+ return
+ self._activation_progress_check()
+
+ def _ac_state_flags_change_callback(self, _nm_ac, _state, _user_data):
+ if self._ctx.is_cancelled():
+ self._activation_clean_up()
+ return
+ self._activation_progress_check()
+
+ def _wait_nm_dev_activation(self):
+ user_data = None
+ self._dev_handlers.add(
+ self._nm_dev.connect(
+ "state-changed", self._dev_state_change_callback, user_data
+ )
+ )
+
+ def _dev_state_change_callback(
+ self, _nm_dev, _new_state, _old_state, _reason, _user_data
+ ):
+ if self._ctx.is_cancelled():
+ self._activation_clean_up()
+ return
+ self._activation_progress_check()
+
+ def _activation_progress_check(self):
+ cur_nm_dev = get_nm_dev(self._ctx, self._iface_name, self._iface_type)
+ if cur_nm_dev and cur_nm_dev != self._nm_dev:
+ logging.debug(
+ f"The NM.Device of profile {self._iface_name} "
+ f"{self._iface_type} changed"
+ )
+ self._remove_dev_handlers()
+ self._nm_dev = cur_nm_dev
+ self._wait_nm_dev_activation()
+
+ if cur_nm_dev:
+ cur_nm_ac = cur_nm_dev.get_active_connection()
+ if cur_nm_ac and cur_nm_ac != self._nm_ac:
+ logging.debug(
+ f"Active connection of device {self._iface_name} "
+ "has been replaced"
+ )
+ self._remove_ac_handlers()
+ self._nm_ac = cur_nm_ac
+ self._wait_nm_ac_activation()
+
+ if is_activated(self._nm_ac, self._nm_dev):
+ logging.debug(
+ "Connection activation succeeded: "
+ f"iface={self._iface_name}, type={self._iface_type}, "
+ f"con_state={self._nm_ac.get_state()}, "
+ f"dev_state={self._nm_dev.get_state()}, "
+ f"state_flags={self._nm_ac.get_state_flags()}"
+ )
+ self._activation_clean_up()
+ self._ctx.finish_async(self._action)
+ elif not is_activating(self._nm_ac, self._nm_dev):
+ reason = f"{self._nm_ac.get_state_reason()}"
+ if self._nm_dev:
+ reason += f" {self._nm_dev.get_state_reason()}"
+ self._activation_clean_up()
+ self._ctx.fail(
+ NmstateLibnmError(f"{self._action} failed: reason={reason}")
+ )
+
+
+class ActiveConnectionDeactivate:
+ def __init__(self, ctx, iface_name, iface_type, nm_ac):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_ac = nm_ac
+
+ def run(self):
+ if self._nm_ac.props.state == NM.ActiveConnectionState.DEACTIVATED:
+ return
+
+ action = f"Deactivate profile: {self._iface_name} {self._iface_type}"
self._ctx.register_async(action)
- handler_id = act_connection.connect(
+ handler_id = self._nm_ac.connect(
NM_AC_STATE_CHANGED_SIGNAL,
self._wait_state_changed_callback,
action,
)
- if act_connection.props.state != NM.ActiveConnectionState.DEACTIVATING:
+ if self._nm_ac.props.state != NM.ActiveConnectionState.DEACTIVATING:
user_data = (handler_id, action)
self._ctx.client.deactivate_connection_async(
- act_connection,
+ self._nm_ac,
self._ctx.cancellable,
self._deactivate_connection_callback,
user_data,
)
- def _wait_state_changed_callback(self, act_con, state, reason, action):
+ def _wait_state_changed_callback(self, nm_ac, state, reason, action):
if self._ctx.is_cancelled():
return
- if act_con.props.state == NM.ActiveConnectionState.DEACTIVATED:
+ if nm_ac.props.state == NM.ActiveConnectionState.DEACTIVATED:
logging.debug(
- "Connection deactivation succeeded on %s", self.devname,
+ "Connection deactivation succeeded on %s",
+ self._iface_name,
)
self._ctx.finish_async(action)
- def _deactivate_connection_callback(self, src_object, result, user_data):
+ def _deactivate_connection_callback(self, nm_client, result, user_data):
handler_id, action = user_data
if self._ctx.is_cancelled():
- if self._act_con:
- self._act_con.handler_disconnect(handler_id)
+ if self._nm_ac:
+ self._nm_ac.handler_disconnect(handler_id)
return
try:
- success = src_object.deactivate_connection_finish(result)
+ success = nm_client.deactivate_connection_finish(result)
except GLib.Error as e:
if e.matches(
NM.ManagerError.quark(), NM.ManagerError.CONNECTIONNOTACTIVE
@@ -114,67 +355,35 @@ class ActiveConnection:
success = True
logging.debug(
"Connection is not active on {}, no need to "
- "deactivate".format(self.devname)
+ "deactivate".format(self._iface_name)
)
- if self._act_con:
- self._act_con.handler_disconnect(handler_id)
+ if self._nm_ac:
+ self._nm_ac.handler_disconnect(handler_id)
self._ctx.finish_async(action)
else:
- if self._act_con:
- self._act_con.handler_disconnect(handler_id)
+ if self._nm_ac:
+ self._nm_ac.handler_disconnect(handler_id)
self._ctx.fail(
NmstateLibnmError(f"{action} failed: error={e}")
)
return
except Exception as e:
- if self._act_con:
- self._act_con.handler_disconnect(handler_id)
+ if self._nm_ac:
+ self._nm_ac.handler_disconnect(handler_id)
self._ctx.fail(
NmstateLibnmError(
- f"BUG: Unexpected error when activating {self.devname} "
- f"error={e}"
+ "BUG: Unexpected error when activating "
+ f"{self._iface_name} error={e}"
)
)
return
if not success:
- if self._act_con:
- self._act_con.handler_disconnect(handler_id)
+ if self._nm_ac:
+ self._nm_ac.handler_disconnect(handler_id)
self._ctx.fail(
NmstateLibnmError(
f"{action} failed: error='None returned from "
"deactivate_connection_finish()'"
)
)
-
- @property
- def nm_active_connection(self):
- return self._act_con
-
- @property
- def devname(self):
- if self._nmdev:
- return self._nmdev.get_iface()
- else:
- return None
-
- @property
- def nmdevice(self):
- return self._nmdev
-
- @nmdevice.setter
- def nmdevice(self, nmdev):
- assert self._nmdev is None
- self._nmdev = nmdev
-
-
-def _is_device_controller_type(nmdev):
- if nmdev:
- is_controller_type = (
- GObject.type_is_a(nmdev, NM.DeviceBond)
- or GObject.type_is_a(nmdev, NM.DeviceBridge)
- or GObject.type_is_a(nmdev, NM.DeviceTeam)
- or GObject.type_is_a(nmdev, NM.DeviceOvsBridge)
- )
- return is_controller_type
- return False
diff --git a/libnmstate/nm/checkpoint.py b/libnmstate/nm/checkpoint.py
index 2927e62..4edfb5a 100644
--- a/libnmstate/nm/checkpoint.py
+++ b/libnmstate/nm/checkpoint.py
@@ -22,11 +22,11 @@ import logging
from libnmstate.error import NmstateConflictError
from libnmstate.error import NmstateLibnmError
from libnmstate.error import NmstatePermissionError
-from libnmstate.ifaces.base_iface import BaseIface
-from libnmstate.nm import profile
-from libnmstate.nm import common
-from libnmstate.schema import Interface
-from .profile_state import is_activated
+
+from .active_connection import is_activating
+from .active_connection import ProfileActivation
+from .common import GLib
+from .common import NM
def get_checkpoints(nm_client):
@@ -54,8 +54,8 @@ class CheckPoint:
devs = []
timeout = self._timeout
cp_flags = (
- common.NM.CheckpointCreateFlags.DELETE_NEW_CONNECTIONS
- | common.NM.CheckpointCreateFlags.DISCONNECT_NEW_DEVICES
+ NM.CheckpointCreateFlags.DELETE_NEW_CONNECTIONS
+ | NM.CheckpointCreateFlags.DISCONNECT_NEW_DEVICES
)
self._ctx.register_async("Create checkpoint")
@@ -71,9 +71,7 @@ class CheckPoint:
self._add_checkpoint_refresh_timeout()
def _add_checkpoint_refresh_timeout(self):
- self._timeout_source = common.GLib.timeout_source_new(
- self._timeout * 500
- )
+ self._timeout_source = GLib.timeout_source_new(self._timeout * 500)
self._timeout_source.set_callback(
self._refresh_checkpoint_timeout, None
)
@@ -94,9 +92,9 @@ class CheckPoint:
self._ctx.client.checkpoint_adjust_rollback_timeout(
self._dbuspath, self._timeout, cancellable, cb, cb_data
)
- return common.GLib.SOURCE_CONTINUE
+ return GLib.SOURCE_CONTINUE
else:
- return common.GLib.SOURCE_REMOVE
+ return GLib.SOURCE_REMOVE
def destroy(self):
if self._dbuspath:
@@ -150,10 +148,10 @@ class CheckPoint:
self._ctx.fail(
NmstateLibnmError(f"Checkpoint create failed: {error_msg}")
)
- except common.GLib.Error as e:
+ except GLib.Error as e:
if e.matches(
- common.NM.ManagerError.quark(),
- common.NM.ManagerError.PERMISSIONDENIED,
+ NM.ManagerError.quark(),
+ NM.ManagerError.PERMISSIONDENIED,
):
self._ctx.fail(
NmstatePermissionError(
@@ -162,8 +160,8 @@ class CheckPoint:
)
)
elif e.matches(
- common.NM.ManagerError.quark(),
- common.NM.ManagerError.INVALIDARGUMENTS,
+ NM.ManagerError.quark(),
+ NM.ManagerError.INVALIDARGUMENTS,
):
self._ctx.fail(
NmstateConflictError(
@@ -200,21 +198,13 @@ class CheckPoint:
if nm_dev and (
(
nm_dev.get_state_reason()
- == common.NM.DeviceStateReason.NEW_ACTIVATION
+ == NM.DeviceStateReason.NEW_ACTIVATION
)
- or nm_dev.get_state() == common.NM.DeviceState.IP_CONFIG
+ or nm_dev.get_state() == NM.DeviceState.IP_CONFIG
):
nm_ac = nm_dev.get_active_connection()
- if not is_activated(nm_ac, nm_dev):
- nm_profile = profile.NmProfile(
- self._ctx, None, BaseIface({Interface.NAME: iface})
- )
- nm_profile.nmdev = nm_dev
- action = f"Waiting for rolling back {iface}"
- self._ctx.register_async(action)
- nm_profile.profile_state.wait_dev_activation(
- action, nm_profile
- )
+ if is_activating(nm_ac, nm_dev):
+ ProfileActivation.wait(self._ctx, nm_ac, nm_dev)
if ret[path] != 0:
logging.error(f"Interface {iface} rollback failed")
else:
diff --git a/libnmstate/nm/connection.py b/libnmstate/nm/connection.py
index d29988e..ec835bb 100644
--- a/libnmstate/nm/connection.py
+++ b/libnmstate/nm/connection.py
@@ -17,12 +17,45 @@
# along with this program. If not, see .
#
+# Handle the NM.SimpleConnection related stuff
+
import uuid
+from libnmstate.schema import Interface
+from libnmstate.schema import InterfaceType
+from libnmstate.schema import LinuxBridge as LB
+from libnmstate.schema import MacVlan
+from libnmstate.schema import MacVtap
+from libnmstate.schema import OVSBridge as OvsB
+from libnmstate.schema import OVSInterface
+from libnmstate.schema import VRF
+
+from libnmstate.ifaces.bridge import BridgeIface
+
+from .bond import create_setting as create_bond_setting
+from .bridge import BRIDGE_TYPE as NM_LINUX_BRIDGE_TYPE
+from .bridge import create_port_setting as create_linux_bridge_port_setting
+from .bridge import create_setting as create_linux_bridge_setting
from .common import NM
-
-
-class ConnectionSetting:
+from .infiniband import create_setting as create_infiniband_setting
+from .ipv4 import create_setting as create_ipv4_setting
+from .ipv6 import create_setting as create_ipv6_setting
+from .lldp import apply_lldp_setting
+from .macvlan import create_setting as create_macvlan_setting
+from .ovs import create_bridge_setting as create_ovs_bridge_setting
+from .ovs import create_interface_setting as create_ovs_interface_setting
+from .ovs import create_port_setting as create_ovs_port_setting
+from .sriov import create_setting as create_sriov_setting
+from .team import create_setting as create_team_setting
+from .translator import Api2Nm
+from .user import create_setting as create_user_setting
+from .vlan import create_setting as create_vlan_setting
+from .vrf import create_vrf_setting
+from .vxlan import create_setting as create_vxlan_setting
+from .wired import create_setting as create_wired_setting
+
+
+class _ConnectionSetting:
def __init__(self, con_setting=None):
self._setting = con_setting
@@ -64,24 +97,117 @@ class ConnectionSetting:
return self._setting
-def get_device_active_connection(nm_device):
- active_conn = None
- if nm_device:
- active_conn = nm_device.get_active_connection()
- return active_conn
+def create_new_nm_simple_conn(iface, nm_profile):
+ nm_iface_type = Api2Nm.get_iface_type(iface.type)
+ iface_info = iface.to_dict()
+ settings = [
+ create_ipv4_setting(iface_info.get(Interface.IPV4), nm_profile),
+ create_ipv6_setting(iface_info.get(Interface.IPV6), nm_profile),
+ ]
+ con_setting = _ConnectionSetting()
+ if nm_profile:
+ con_setting.import_by_profile(nm_profile)
+ con_setting.set_profile_name(iface.name)
+ else:
+ con_setting.create(iface.name, iface.name, nm_iface_type)
+
+ apply_lldp_setting(con_setting, iface_info)
+
+ controller = iface.controller
+ controller_type = iface.controller_type
+ if controller_type == InterfaceType.LINUX_BRIDGE:
+ controller_type = NM_LINUX_BRIDGE_TYPE
+ con_setting.set_controller(controller, controller_type)
+ settings.append(con_setting.setting)
+
+ # Only apply wired/ethernet configuration based on original desire
+ # state rather than the merged one.
+ original_state_wired = {}
+ if iface.is_desired:
+ original_state_wired = iface.original_dict
+ if iface.type != InterfaceType.INFINIBAND:
+ # The IP over InfiniBand has its own setting for MTU and does not
+ # have ethernet layer.
+ wired_setting = create_wired_setting(original_state_wired, nm_profile)
+
+ if wired_setting:
+ settings.append(wired_setting)
+
+ user_setting = create_user_setting(iface_info, nm_profile)
+ if user_setting:
+ settings.append(user_setting)
+
+ if iface.type == InterfaceType.BOND:
+ settings.append(create_bond_setting(iface, wired_setting, nm_profile))
+ elif iface.type == InterfaceType.LINUX_BRIDGE:
+ bridge_config = iface_info.get(LB.CONFIG_SUBTREE, {})
+ bridge_options = bridge_config.get(LB.OPTIONS_SUBTREE)
+ bridge_ports = bridge_config.get(LB.PORT_SUBTREE)
+ if bridge_options or bridge_ports:
+ linux_bridge_setting = create_linux_bridge_setting(
+ iface_info,
+ nm_profile,
+ iface.original_dict,
+ )
+ settings.append(linux_bridge_setting)
+ elif iface.type == InterfaceType.OVS_BRIDGE:
+ ovs_bridge_state = iface_info.get(OvsB.CONFIG_SUBTREE, {})
+ ovs_bridge_options = ovs_bridge_state.get(OvsB.OPTIONS_SUBTREE)
+ if ovs_bridge_options:
+ settings.append(create_ovs_bridge_setting(ovs_bridge_options))
+ elif iface.type == InterfaceType.OVS_PORT:
+ ovs_port_options = iface_info.get(OvsB.OPTIONS_SUBTREE)
+ settings.append(create_ovs_port_setting(ovs_port_options))
+ elif iface.type == InterfaceType.OVS_INTERFACE:
+ patch_state = iface_info.get(OVSInterface.PATCH_CONFIG_SUBTREE)
+ settings.extend(create_ovs_interface_setting(patch_state))
+ elif iface.type == InterfaceType.INFINIBAND:
+ ib_setting = create_infiniband_setting(
+ iface_info,
+ nm_profile,
+ iface.original_dict,
+ )
+ if ib_setting:
+ settings.append(ib_setting)
+
+ bridge_port_options = iface_info.get(BridgeIface.BRPORT_OPTIONS_METADATA)
+ if (
+ bridge_port_options
+ and iface.controller_type == InterfaceType.LINUX_BRIDGE
+ ):
+ settings.append(
+ create_linux_bridge_port_setting(bridge_port_options, nm_profile)
+ )
+
+ vlan_setting = create_vlan_setting(iface_info, nm_profile)
+ if vlan_setting:
+ settings.append(vlan_setting)
+
+ vxlan_setting = create_vxlan_setting(iface_info, nm_profile)
+ if vxlan_setting:
+ settings.append(vxlan_setting)
+ sriov_setting = create_sriov_setting(iface_info, nm_profile)
+ if sriov_setting:
+ settings.append(sriov_setting)
-def list_connections_by_ifname(context, ifname):
- return [
- con
- for con in context.client.get_connections()
- if con.get_interface_name() == ifname
- ]
+ team_setting = create_team_setting(iface_info, nm_profile)
+ if team_setting:
+ settings.append(team_setting)
+ if VRF.CONFIG_SUBTREE in iface_info:
+ settings.append(create_vrf_setting(iface_info[VRF.CONFIG_SUBTREE]))
+
+ if MacVlan.CONFIG_SUBTREE in iface_info:
+ settings.append(create_macvlan_setting(iface_info, nm_profile))
+
+ if MacVtap.CONFIG_SUBTREE in iface_info:
+ settings.append(
+ create_macvlan_setting(iface_info, nm_profile, tap=True)
+ )
-def create_new_simple_connection(settings):
- simple_conn = NM.SimpleConnection.new()
+ nm_simple_conn = NM.SimpleConnection.new()
for setting in settings:
- simple_conn.add_setting(setting)
+ nm_simple_conn.add_setting(setting)
- return simple_conn
+ return nm_simple_conn
diff --git a/libnmstate/nm/context.py b/libnmstate/nm/context.py
index 373ffe8..9db8592 100644
--- a/libnmstate/nm/context.py
+++ b/libnmstate/nm/context.py
@@ -55,6 +55,10 @@ class NmContext:
self._init_queue()
self._init_cancellable()
+ def _init_client(self):
+ self._client = NM.Client.new(cancellable=None)
+ self._context = self._client.get_main_context()
+
def _init_queue(self):
self._fast_queue = set()
self._slow_queue = set()
@@ -208,6 +212,8 @@ class NmContext:
if self._error:
# The queue and error should be flush and perpare for another run
+ self._cancellable.cancel()
+ self.refresh_content()
self._init_queue()
self._init_cancellable()
tmp_error = self._error
@@ -215,6 +221,3 @@ class NmContext:
# pylint: disable=raising-bad-type
raise tmp_error
# pylint: enable=raising-bad-type
-
- def get_nm_dev(self, iface_name):
- return self.client.get_device_by_iface(iface_name)
diff --git a/libnmstate/nm/device.py b/libnmstate/nm/device.py
index 66e194e..b2f4d17 100644
--- a/libnmstate/nm/device.py
+++ b/libnmstate/nm/device.py
@@ -20,122 +20,125 @@
import logging
from libnmstate.error import NmstateLibnmError
+from libnmstate.schema import InterfaceType
-from . import active_connection as ac
-from . import profile_state
from .common import NM
from .common import GLib
+from .macvlan import is_macvtap
+from .translator import Nm2Api
NM_DBUS_INTERFACE_DEVICE = f"{NM.DBUS_INTERFACE}.Device"
NM_USE_DEFAULT_TIMEOUT_VALUE = -1
-def deactivate(context, dev):
- """
- Deactivating the current active connection,
- The profile itself is not removed.
-
- For software devices, deactivation removes the devices from the kernel.
- """
- act_con = ac.ActiveConnection(context)
- act_con.nmdevice = dev
- act_con.import_by_device()
- act_con.deactivate()
-
-
-def modify(context, nm_profile):
- """
- Modify the given connection profile on the device.
- Implemented by the reapply operation with a fallback to the
- connection profile activation.
- """
- nm_ac = nm_profile.nmdev.get_active_connection()
- if profile_state.is_activated(nm_ac, nm_profile.nmdev):
+class DeviceReapply:
+ def __init__(
+ self,
+ ctx,
+ iface_name,
+ iface_type,
+ nm_dev,
+ nm_simple_conn,
+ profile_activation,
+ ):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_dev = nm_dev
+ self._nm_simple_conn = nm_simple_conn
+ self._profile_activation = profile_activation
+
+ def run(self):
+ """
+ Modify the given connection profile on the device without bring the
+ interface down.
+ If failed, fall back to normal profile activation
+ """
version_id = 0
flags = 0
- action = f"Reapply device config: {nm_profile.nmdev.get_iface()}"
- context.register_async(action)
- user_data = context, nm_profile, action
- nm_profile.nmdev.reapply_async(
- nm_profile.profile,
+ action = (
+ f"Reapply device config: {self._iface_name} {self._iface_type} "
+ f"{self._nm_simple_conn.get_uuid()}"
+ )
+ self._ctx.register_async(action)
+ user_data = action
+ self._nm_dev.reapply_async(
+ self._nm_simple_conn,
version_id,
flags,
- context.cancellable,
- _modify_callback,
+ self._ctx.cancellable,
+ self._reapply_callback,
user_data,
)
- else:
- _activate_async(context, nm_profile)
-
-
-def _modify_callback(src_object, result, user_data):
- context, nm_profile, action = user_data
- if context.is_cancelled():
- return
- devname = src_object.get_iface()
- try:
- success = src_object.reapply_finish(result)
- except Exception as e:
- logging.debug(
- "Device reapply failed on %s: error=%s\n"
- "Fallback to device activation",
- devname,
- e,
- )
- context.finish_async(action, suppress_log=True)
- _activate_async(context, nm_profile)
- return
-
- if success:
- context.finish_async(action)
- else:
- logging.debug(
- "Device reapply failed, fallback to device activation: dev=%s, "
- "error='None returned from reapply_finish()'",
- devname,
- )
- context.finish_async(action, suppress_log=True)
- _activate_async(context, nm_profile)
-
-def _activate_async(context, nm_profile):
- if nm_profile.nmdev:
- # Workaround of https://bugzilla.redhat.com/show_bug.cgi?id=1772470
- mark_device_as_managed(context, nm_profile.nmdev)
- nm_profile.activate()
-
-
-def delete_device(context, nmdev):
- iface_name = nmdev.get_iface()
- if iface_name:
- action = f"Delete device: {nmdev.get_iface()}"
- user_data = context, nmdev, action, nmdev.get_iface()
- context.register_async(action)
- nmdev.delete_async(
- context.cancellable, _delete_device_callback, user_data
+ def _reapply_callback(self, nm_dev, result, user_data):
+ action = user_data
+ if self._ctx.is_cancelled():
+ return
+ try:
+ success = nm_dev.reapply_finish(result)
+ except Exception as e:
+ logging.debug(
+ f"Device reapply failed on {self._iface_name} "
+ f"{self._iface_type}: error={e}, "
+ "Fallback to device activation"
+ )
+ self._ctx.finish_async(action, suppress_log=True)
+ self._profile_activation.run()
+ return
+
+ if success:
+ self._ctx.finish_async(action)
+ else:
+ logging.debug(
+ "Device reapply failed, fallback to device activation: "
+ f"iface={self._iface_name}, type={self._iface_type} "
+ "error='None returned from reapply_finish()'"
+ )
+ self._ctx.finish_async(action, suppress_log=True)
+ self._profile_activation.run()
+
+
+class DeviceDelete:
+ def __init__(self, ctx, iface_name, iface_type, nm_dev):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_dev = nm_dev
+
+ def run(self):
+ action = f"Delete device: {self._iface_type} {self._iface_name}"
+ user_data = action
+ self._ctx.register_async(action)
+ self._nm_dev.delete_async(
+ self._ctx.cancellable, self._delete_device_callback, user_data
)
-
-def _delete_device_callback(src_object, result, user_data):
- context, nmdev, action, iface_name = user_data
- if context.is_cancelled():
- return
- error = None
- try:
- src_object.delete_finish(result)
- except Exception as e:
- error = e
-
- if not nmdev.is_real():
- logging.debug("Interface is not real anymore: iface=%s", iface_name)
- if error:
- logging.debug("Ignored error: %s", error)
- context.finish_async(action)
- else:
- context.fail(
- NmstateLibnmError(f"{action} failed: error={error or 'unknown'}")
- )
+ def _delete_device_callback(self, nm_dev, result, user_data):
+ action = user_data
+ if self._ctx.is_cancelled():
+ return
+ error = None
+ try:
+ nm_dev.delete_finish(result)
+ except Exception as e:
+ error = e
+
+ if not nm_dev.is_real():
+ logging.debug(
+ f"Interface is deleted and not real/exist anymore: "
+ f"iface={self._iface_name} type={self._iface_type}"
+ )
+ if error:
+ logging.debug(f"Ignored error: {error}")
+ self._ctx.finish_async(action)
+ else:
+ self._ctx.fail(
+ NmstateLibnmError(
+ f"{action} failed: error={error or 'unknown'}"
+ )
+ )
def list_devices(client):
@@ -151,8 +154,8 @@ def get_device_common_info(dev):
}
-def is_externally_managed(nmdev):
- nm_ac = nmdev.get_active_connection()
+def is_externally_managed(nm_dev):
+ nm_ac = nm_dev.get_active_connection()
return nm_ac and NM.ActivationStateFlags.EXTERNAL & nm_ac.get_state_flags()
@@ -178,3 +181,36 @@ def _set_managed_callback(_src_object, _result, user_data):
# There is no document mention this action might fail
# If anything goes wrong, we trust verifcation stage can detect it.
context.finish_async(action)
+
+
+def get_iface_type(nm_dev):
+ # TODO: Below code are mimic from translator, need redesign on this
+ iface_type = nm_dev.get_type_description()
+ if iface_type != InterfaceType.ETHERNET:
+ iface_type = Nm2Api.get_iface_type(nm_dev.get_type_description())
+ if iface_type == InterfaceType.MAC_VLAN:
+ # Check whether we are MAC VTAP as NM is treating both of them as
+ # MAC VLAN.
+ # BUG: We should use applied config here.
+ nm_ac = nm_dev.get_active_connection()
+ if nm_ac:
+ nm_profile = nm_ac.get_connection()
+ if nm_profile and is_macvtap(nm_profile):
+ iface_type = InterfaceType.MAC_VTAP
+ return iface_type
+
+
+def get_nm_dev(ctx, iface_name, iface_type):
+ """
+ Return the first NM.Device matching iface_name and iface_type.
+ We don't use `NM.Client.get_device_by_iface()` as nm_dev does not
+ kernel interface, it could be OVS bridge or OVS port where name
+ can duplicate with kernel interface name.
+ """
+ for nm_dev in ctx.client.get_devices():
+ cur_iface_type = get_iface_type(nm_dev)
+ if nm_dev.get_iface() == iface_name and (
+ iface_type is None or cur_iface_type == iface_type
+ ):
+ return nm_dev
+ return None
diff --git a/libnmstate/nm/dns.py b/libnmstate/nm/dns.py
index 8085770..9fb14d8 100644
--- a/libnmstate/nm/dns.py
+++ b/libnmstate/nm/dns.py
@@ -23,7 +23,6 @@ from operator import itemgetter
from libnmstate import iplib
from libnmstate.dns import DnsState
from libnmstate.error import NmstateInternalError
-from libnmstate.nm import active_connection as nm_ac
from libnmstate.schema import DNS
from libnmstate.schema import Interface
@@ -131,7 +130,13 @@ def get_dns_config_iface_names(acs_and_ipv4_profiles, acs_and_ipv6_profiles):
Return a list of interface names which hold static DNS configuration.
"""
iface_names = []
- for ac, ip_profile in chain(acs_and_ipv6_profiles, acs_and_ipv4_profiles):
+ for nm_ac, ip_profile in chain(
+ acs_and_ipv6_profiles, acs_and_ipv4_profiles
+ ):
if ip_profile.props.dns or ip_profile.props.dns_search:
- iface_names.append(nm_ac.ActiveConnection(nm_ac_con=ac).devname)
+ try:
+ iface_name = nm_ac.get_devices()[0].get_iface()
+ iface_names.append(iface_name)
+ except IndexError:
+ continue
return iface_names
diff --git a/libnmstate/nm/ipv4.py b/libnmstate/nm/ipv4.py
index efdfce0..baf2e3a 100644
--- a/libnmstate/nm/ipv4.py
+++ b/libnmstate/nm/ipv4.py
@@ -154,7 +154,3 @@ def is_dynamic(active_connection):
if ip_profile:
return ip_profile.get_method() == NM.SETTING_IP4_CONFIG_METHOD_AUTO
return False
-
-
-def get_routing_rule_config(nm_client):
- return nm_route.get_routing_rule_config(acs_and_ip_profiles(nm_client))
diff --git a/libnmstate/nm/ipv6.py b/libnmstate/nm/ipv6.py
index 55b508c..8e01fd7 100644
--- a/libnmstate/nm/ipv6.py
+++ b/libnmstate/nm/ipv6.py
@@ -86,6 +86,7 @@ def create_setting(config, base_con_profile):
setting_ip.props.never_default = False
setting_ip.props.ignore_auto_dns = False
setting_ip.clear_routes()
+ setting_ip.clear_routing_rules()
setting_ip.props.gateway = None
setting_ip.props.route_table = Route.USE_DEFAULT_ROUTE_TABLE
setting_ip.props.route_metric = Route.USE_DEFAULT_METRIC
@@ -208,7 +209,3 @@ def is_dynamic(active_connection):
NM.SETTING_IP6_CONFIG_METHOD_DHCP,
)
return False
-
-
-def get_routing_rule_config(nm_client):
- return nm_route.get_routing_rule_config(acs_and_ip_profiles(nm_client))
diff --git a/libnmstate/nm/macvlan.py b/libnmstate/nm/macvlan.py
index daee9ba..ca049fa 100644
--- a/libnmstate/nm/macvlan.py
+++ b/libnmstate/nm/macvlan.py
@@ -18,7 +18,10 @@
#
from libnmstate.error import NmstateValueError
+from libnmstate.schema import Interface
+from libnmstate.schema import InterfaceType
from libnmstate.schema import MacVlan
+from libnmstate.schema import MacVtap
from .common import NM
@@ -31,11 +34,12 @@ NMSTATE_MODE_TO_NM_MODE = {
}
-def create_setting(iface_state, base_con_profile):
- macvlan = iface_state.get(MacVlan.CONFIG_SUBTREE)
- if not macvlan:
- return None
-
+def create_setting(iface_state, base_con_profile, tap=False):
+ macvlan = (
+ iface_state.get(MacVtap.CONFIG_SUBTREE)
+ if tap
+ else iface_state.get(MacVlan.CONFIG_SUBTREE)
+ )
macvlan_setting = None
if base_con_profile:
macvlan_setting = base_con_profile.get_setting_by_name(
@@ -56,7 +60,31 @@ def create_setting(iface_state, base_con_profile):
macvlan_setting.props.mode = nm_mode
macvlan_setting.props.parent = macvlan[MacVlan.BASE_IFACE]
+ macvlan_setting.props.tap = tap
if macvlan.get(MacVlan.PROMISCUOUS) is not None:
macvlan_setting.props.promiscuous = macvlan[MacVlan.PROMISCUOUS]
return macvlan_setting
+
+
+def is_macvtap(applied_config):
+ if applied_config:
+ macvlan_setting = applied_config.get_setting_by_name(
+ NM.SETTING_MACVLAN_SETTING_NAME
+ )
+ if macvlan_setting:
+ return macvlan_setting.props.tap
+ return False
+
+
+def get_current_macvlan_type(applied_config):
+ """
+ This is a workaround needed due to Nmstate gathering the interface type
+ from NetworkManager, as we are deciding the interface type using the
+ setting name. If the interface type is not adjusted, Nmstate will fail
+ during verification as NM and Nispor interfaces will not be merged
+ correctly.
+ """
+ if is_macvtap(applied_config):
+ return {Interface.TYPE: InterfaceType.MAC_VTAP}
+ return {}
diff --git a/libnmstate/nm/ovs.py b/libnmstate/nm/ovs.py
index 068f718..cdca1f5 100644
--- a/libnmstate/nm/ovs.py
+++ b/libnmstate/nm/ovs.py
@@ -26,6 +26,7 @@ from libnmstate.schema import OVSBridge as OB
from libnmstate.schema import OVSInterface
from libnmstate.ifaces import ovs
from libnmstate.ifaces.bridge import BridgeIface
+from libnmstate.ifaces.ovs import OvsPortIface
from .common import NM
@@ -124,10 +125,6 @@ def create_patch_setting(patch_state):
return patch_setting
-def is_ovs_port_type_id(type_id):
- return type_id == NM.DeviceType.OVS_PORT
-
-
def get_ovs_bridge_info(nm_dev_ovs_br):
iface_info = {OB.CONFIG_SUBTREE: {}}
ports_info = _get_bridge_nmstate_ports_info(nm_dev_ovs_br)
@@ -273,44 +270,21 @@ def _get_bridge_options(bridge_device):
return bridge_options
-def create_ovs_proxy_iface_info(iface):
- """
- Prepare the state of the "proxy" interface. These are interfaces that
- exist as NM entities/profiles, but are invisible to the API.
- These proxy interfaces state is created as a side effect of other
- ifaces definition.
- In OVS case, the port profile is the proxy, it is not part of the
- public state of the system, but internal to the NM provider.
- """
- iface_info = iface.to_dict()
- controller_type = iface_info.get(CONTROLLER_TYPE_METADATA)
- if controller_type != InterfaceType.OVS_BRIDGE:
- return None
- port_opts_metadata = iface_info.get(BridgeIface.BRPORT_OPTIONS_METADATA)
- if port_opts_metadata is None:
- return None
- port_iface_desired_state = _create_ovs_port_iface_desired_state(
- port_opts_metadata, iface, iface_info
- )
- # The "visible" port/interface needs to point to the port profile
- iface.set_controller(
- port_iface_desired_state[Interface.NAME], InterfaceType.OVS_PORT
- )
-
- return port_iface_desired_state
-
-
-def _create_ovs_port_iface_desired_state(port_options, iface, iface_info):
+def create_iface_for_nm_ovs_port(iface):
iface_name = iface.name
+ iface_info = iface.to_dict()
+ port_options = iface_info.get(BridgeIface.BRPORT_OPTIONS_METADATA)
if ovs.is_ovs_lag_port(port_options):
port_name = port_options[OB.Port.NAME]
else:
port_name = PORT_PROFILE_PREFIX + iface_name
- return {
- Interface.NAME: port_name,
- Interface.TYPE: InterfaceType.OVS_PORT,
- Interface.STATE: iface_info[Interface.STATE],
- OB.OPTIONS_SUBTREE: port_options,
- CONTROLLER_METADATA: iface_info[CONTROLLER_METADATA],
- CONTROLLER_TYPE_METADATA: iface_info[CONTROLLER_TYPE_METADATA],
- }
+ return OvsPortIface(
+ {
+ Interface.NAME: port_name,
+ Interface.TYPE: InterfaceType.OVS_PORT,
+ Interface.STATE: iface.state,
+ OB.OPTIONS_SUBTREE: port_options,
+ CONTROLLER_METADATA: iface_info[CONTROLLER_METADATA],
+ CONTROLLER_TYPE_METADATA: iface_info[CONTROLLER_TYPE_METADATA],
+ }
+ )
diff --git a/libnmstate/nm/plugin.py b/libnmstate/nm/plugin.py
index 36d0d6f..d7a7961 100644
--- a/libnmstate/nm/plugin.py
+++ b/libnmstate/nm/plugin.py
@@ -27,28 +27,33 @@ from libnmstate.schema import DNS
from libnmstate.schema import Interface
from libnmstate.schema import InterfaceType
from libnmstate.schema import Route
-from libnmstate.schema import RouteRule
from libnmstate.plugin import NmstatePlugin
-from . import connection as nm_connection
-from . import device as nm_device
-from . import ipv4 as nm_ipv4
-from . import ipv6 as nm_ipv6
-from . import lldp as nm_lldp
-from . import ovs as nm_ovs
-from . import translator as nm_translator
-from . import wired as nm_wired
-from . import user as nm_user
-from . import team as nm_team
-from . import dns as nm_dns
+
from .checkpoint import CheckPoint
from .checkpoint import get_checkpoints
from .common import NM
from .context import NmContext
-from .profile import get_all_applied_configs
-from .profile import NmProfiles
-from .route import get_running_config as get_route_running_config
+from .device import get_device_common_info
+from .device import list_devices
+from .dns import get_running as get_dns_running
+from .dns import get_running_config as get_dns_running_config
from .infiniband import get_info as get_infiniband_info
+from .ipv4 import get_info as get_ipv4_info
+from .ipv6 import get_info as get_ipv6_info
+from .lldp import get_info as get_lldp_info
+from .macvlan import get_current_macvlan_type
+from .ovs import get_interface_info as get_ovs_interface_info
+from .ovs import get_ovs_bridge_info
+from .ovs import has_ovs_capability
+from .profiles import NmProfiles
+from .profiles import get_all_applied_configs
+from .route import get_running_config as get_route_running_config
+from .team import get_info as get_team_info
+from .team import has_team_capability
+from .translator import Nm2Api
+from .user import get_info as get_user_info
+from .wired import get_info as get_wired_info
class NetworkManagerPlugin(NmstatePlugin):
@@ -92,9 +97,9 @@ class NetworkManagerPlugin(NmstatePlugin):
@property
def capabilities(self):
capabilities = []
- if nm_ovs.has_ovs_capability(self.client) and is_ovs_running():
+ if has_ovs_capability(self.client) and is_ovs_running():
capabilities.append(NmstatePlugin.OVS_CAPABILITY)
- if nm_team.has_team_capability(self.client):
+ if has_team_capability(self.client):
capabilities.append(NmstatePlugin.TEAM_CAPABILITY)
return capabilities
@@ -109,47 +114,39 @@ class NetworkManagerPlugin(NmstatePlugin):
def get_interfaces(self):
info = []
- capabilities = self.capabilities
applied_configs = self._applied_configs
devices_info = [
- (dev, nm_device.get_device_common_info(dev))
- for dev in nm_device.list_devices(self.client)
+ (dev, get_device_common_info(dev))
+ for dev in list_devices(self.client)
]
for dev, devinfo in devices_info:
if not dev.get_managed():
# Skip unmanaged interface
continue
- type_id = devinfo["type_id"]
- iface_info = nm_translator.Nm2Api.get_common_device_info(devinfo)
+ iface_info = Nm2Api.get_common_device_info(devinfo)
applied_config = applied_configs.get(iface_info[Interface.NAME])
- act_con = nm_connection.get_device_active_connection(dev)
- iface_info[Interface.IPV4] = nm_ipv4.get_info(
- act_con, applied_config
- )
- iface_info[Interface.IPV6] = nm_ipv6.get_info(
- act_con, applied_config
- )
- iface_info.update(nm_wired.get_info(dev))
- iface_info.update(nm_user.get_info(self.context, dev))
- iface_info.update(nm_lldp.get_info(self.client, dev))
- iface_info.update(nm_team.get_info(dev))
+ act_con = dev.get_active_connection()
+ iface_info[Interface.IPV4] = get_ipv4_info(act_con, applied_config)
+ iface_info[Interface.IPV6] = get_ipv6_info(act_con, applied_config)
+ iface_info.update(get_wired_info(dev))
+ iface_info.update(get_user_info(self.context, dev))
+ iface_info.update(get_lldp_info(self.client, dev))
+ iface_info.update(get_team_info(dev))
iface_info.update(get_infiniband_info(applied_config))
-
- if NmstatePlugin.OVS_CAPABILITY in capabilities:
- if iface_info[Interface.TYPE] == InterfaceType.OVS_BRIDGE:
- iface_info.update(nm_ovs.get_ovs_bridge_info(dev))
- iface_info = _remove_ovs_bridge_unsupported_entries(
- iface_info
- )
- elif iface_info[Interface.TYPE] == InterfaceType.OVS_INTERFACE:
- iface_info.update(nm_ovs.get_interface_info(act_con))
- elif nm_ovs.is_ovs_port_type_id(type_id):
- continue
+ iface_info.update(get_current_macvlan_type(applied_config))
+
+ if iface_info[Interface.TYPE] == InterfaceType.OVS_BRIDGE:
+ iface_info.update(get_ovs_bridge_info(dev))
+ iface_info = _remove_ovs_bridge_unsupported_entries(iface_info)
+ elif iface_info[Interface.TYPE] == InterfaceType.OVS_INTERFACE:
+ iface_info.update(get_ovs_interface_info(act_con))
+ elif iface_info[Interface.TYPE] == InterfaceType.OVS_PORT:
+ continue
info.append(iface_info)
@@ -161,17 +158,15 @@ class NetworkManagerPlugin(NmstatePlugin):
return {Route.CONFIG: get_route_running_config(self._applied_configs)}
def get_route_rules(self):
- return {
- RouteRule.CONFIG: (
- nm_ipv4.get_routing_rule_config(self.client)
- + nm_ipv6.get_routing_rule_config(self.client)
- )
- }
+ """
+ Nispor will provide running config of route rule from kernel.
+ """
+ return {}
def get_dns_client_config(self):
return {
- DNS.RUNNING: nm_dns.get_running(self.client),
- DNS.CONFIG: nm_dns.get_running_config(self._applied_configs),
+ DNS.RUNNING: get_dns_running(self.client),
+ DNS.CONFIG: get_dns_running_config(self._applied_configs),
}
def refresh_content(self):
diff --git a/libnmstate/nm/profile.py b/libnmstate/nm/profile.py
index 6bd70e5..6be1588 100644
--- a/libnmstate/nm/profile.py
+++ b/libnmstate/nm/profile.py
@@ -21,323 +21,195 @@
# * NM.RemoteConnection, NM.SimpleConnection releated
from distutils.version import StrictVersion
-import logging
+from libnmstate.error import NmstateLibnmError
from libnmstate.error import NmstateNotSupportedError
-from libnmstate.error import NmstateValueError
+from libnmstate.error import NmstateInternalError
from libnmstate.schema import Interface
-from libnmstate.schema import InterfaceState
from libnmstate.schema import InterfaceType
-from libnmstate.schema import LinuxBridge as LB
-from libnmstate.schema import OVSBridge as OvsB
-from libnmstate.schema import OVSInterface
-from libnmstate.schema import Team
-from libnmstate.schema import VRF
-from libnmstate.ifaces.base_iface import BaseIface
-from libnmstate.ifaces.bond import BondIface
-from libnmstate.ifaces.bridge import BridgeIface
-
-from . import bond
-from . import bridge
-from . import connection
-from . import device
-from . import dns as nm_dns
-from . import ipv4
-from . import ipv6
-from . import lldp
-from . import macvlan
-from . import ovs as nm_ovs
-from . import profile_state
-from . import sriov
-from . import team
-from . import translator
-from . import user
-from . import vlan
-from . import vxlan
-from . import wired
+from libnmstate.schema import Ethernet
+from .active_connection import ActiveConnectionDeactivate
+from .active_connection import ProfileActivation
+from .active_connection import is_activated
from .common import NM
-from .device import mark_device_as_managed
-from .device import list_devices
-from .device import is_externally_managed
-from .vrf import create_vrf_setting
-from .infiniband import create_setting as create_infiniband_setting
-
-
-ACTION_DEACTIVATE_BEFOREHAND = "deactivate-beforehand"
-ACTION_DELETE_PROFILE = "delete-profile"
-ACTION_ACTIVATE = "activate"
-ACTION_MODIFY = "modify"
-ACTION_DEACTIVATE = "deactivate"
-ACTION_DELETE_DEV_PROFILES = "delete-dev-profiles"
-ACTION_DELETE_DEV = "delete-dev"
-
-CONTROLLER_METADATA = "_controller"
-CONTROLLER_TYPE_METADATA = "_controller_type"
-CONTROLLER_IFACE_TYPES = (
- InterfaceType.OVS_BRIDGE,
- bond.BOND_TYPE,
- LB.TYPE,
- Team.TYPE,
-)
-
-
-class NmProfiles:
- def __init__(self, context):
- self._ctx = context
-
- def apply_config(self, net_state, save_to_disk):
- self._prepare_state_for_profiles(net_state)
- self._profiles = [
- NmProfile(self._ctx, save_to_disk, iface)
- for iface in net_state.ifaces.values()
- if (iface.is_changed or iface.is_desired) and not iface.is_ignore
- ]
-
- for profile in self._profiles:
- profile.store_config()
- self._ctx.wait_all_finish()
-
- grouped_profiles = self._group_profile_by_action_order()
- for profile_group in grouped_profiles:
- for profile in profile_group:
- profile.apply_config()
- self._ctx.wait_all_finish()
-
- def _group_profile_by_action_order(self):
- groups = {
- "profiles_to_deactivate_beforehand": set(),
- "profiles_to_delete": set(),
- "new_controller_not_as_port": set(),
- "new_ifaces_to_activate": set(),
- "controller_ifaces_to_edit": set(),
- "new_ovs_port_to_activate": set(),
- "new_ovs_interface_to_activate": set(),
- "ifaces_to_edit": set(),
- "new_vlan_x_to_activate": set(),
- "profiles_to_deactivate": set(),
- "devs_to_delete_profile": set(),
- "devs_to_delete": set(),
- }
-
- for profile in self._profiles:
- profile.classify_profile_for_actions(groups)
-
- return groups.values()
-
- def _prepare_state_for_profiles(self, net_state):
- _preapply_dns_fix_for_profiles(self._ctx, net_state)
- _mark_nm_external_subordinate_changed(self._ctx, net_state)
- _mark_mode_changed_bond_child_interface_as_changed(net_state)
-
- proxy_ifaces = {}
- for iface in net_state.ifaces.values():
- proxy_iface_info = nm_ovs.create_ovs_proxy_iface_info(iface)
- if proxy_iface_info:
- proxy_iface = BaseIface(proxy_iface_info)
- proxy_iface.mark_as_changed()
- proxy_ifaces[proxy_iface.name] = proxy_iface
- net_state.ifaces.update(proxy_ifaces)
+from .connection import create_new_nm_simple_conn
+from .device import get_nm_dev
+from .device import DeviceReapply
+from .device import DeviceDelete
+from .translator import Api2Nm
class NmProfile:
- def __init__(self, context, save_to_disk, iface=None):
- self._ctx = context
+ # For unmanged iface and desired to down
+ ACTION_ACTIVATE_FIRST = "activate_first"
+ ACTION_DEACTIVATE = "deactivate"
+ ACTION_DEACTIVATE_FIRST = "deactivate_first"
+ ACTION_DELETE_DEVICE = "delete_device"
+ ACTION_MODIFIED = "modified"
+ ACTION_NEW_IFACES = "new_ifaces"
+ ACTION_NEW_OVS_IFACE = "new_ovs_iface"
+ ACTION_NEW_OVS_PORT = "new_ovs_port"
+ ACTION_NEW_VLAN = "new_vlan"
+ ACTION_NEW_VXLAN = "new_vxlan"
+ ACTION_OTHER_MASTER = "other_master"
+ ACTION_DELETE_PROFILE = "delete_profile"
+ ACTION_TOP_MASTER = "top_master"
+
+ # This is order on group for activation/deactivation
+ ACTIONS = (
+ ACTION_ACTIVATE_FIRST,
+ ACTION_DEACTIVATE_FIRST,
+ ACTION_TOP_MASTER,
+ ACTION_NEW_IFACES,
+ ACTION_OTHER_MASTER,
+ ACTION_NEW_OVS_PORT,
+ ACTION_NEW_OVS_IFACE,
+ ACTION_MODIFIED,
+ ACTION_NEW_VLAN,
+ ACTION_NEW_VXLAN,
+ ACTION_DEACTIVATE,
+ ACTION_DELETE_PROFILE,
+ ACTION_DELETE_DEVICE,
+ )
+
+ def __init__(self, ctx, iface, save_to_disk):
+ self._ctx = ctx
self._iface = iface
self._save_to_disk = save_to_disk
- self._nmdev = None
+ self._nm_iface_type = None
+ if self._iface.type != InterfaceType.UNKNOWN:
+ self._nm_iface_type = Api2Nm.get_iface_type(self._iface.type)
self._nm_ac = None
- self._nm_profile_state = profile_state.NmProfileState(context)
- self._remote_conn = None
- self._simple_conn = None
- self._actions_needed = []
-
- @property
- def iface_info(self):
- return self._iface.to_dict()
-
- @property
- def iface(self):
- return self._iface
-
- @property
- def original_iface_info(self):
- return self._iface.original_dict
-
- @property
- def profile_state(self):
- return self._nm_profile_state
-
- @property
- def nmdev(self):
- if self._nmdev:
- return self._nmdev
- elif self.devname:
- return self._ctx.get_nm_dev(self.devname)
- else:
- return None
-
- @nmdev.setter
- def nmdev(self, dev):
- self._nmdev = dev
-
- @property
- def nm_ac(self):
- return self._nm_ac
-
- @nm_ac.setter
- def nm_ac(self, ac):
- self._nm_ac = ac
-
- @property
- def remote_conn(self):
- return self._remote_conn
-
- @remote_conn.setter
- def remote_conn(self, con):
- self._remote_conn = con
-
- @property
- def simple_conn(self):
- return self._simple_conn
-
- @property
- def uuid(self):
- if self._remote_conn:
- return self._remote_conn.get_uuid()
- elif self._simple_conn:
- return self._simple_conn.get_uuid()
- else:
- return self.iface.name
-
- @property
- def devname(self):
- if self._remote_conn:
- return self._remote_conn.get_interface_name()
- elif self._simple_conn:
- return self._simple_conn.get_interface_name()
- else:
- return self.iface.name
-
- @property
- def profile(self):
- return self._simple_conn if self._simple_conn else self._remote_conn
-
- @property
- def is_memory_only(self):
- if self._remote_conn:
- profile_flags = self._remote_conn.get_flags()
- return (
- NM.SettingsConnectionFlags.UNSAVED & profile_flags
- or NM.SettingsConnectionFlags.VOLATILE & profile_flags
- )
- return False
-
- def apply_config(self):
- if ACTION_DEACTIVATE_BEFOREHAND in self._actions_needed:
- device.deactivate(self._ctx, self.nmdev)
- elif ACTION_DELETE_PROFILE in self._actions_needed:
- self.delete()
- elif ACTION_ACTIVATE in self._actions_needed:
- self.activate()
- elif ACTION_MODIFY in self._actions_needed:
- device.modify(self._ctx, self)
- elif ACTION_DEACTIVATE in self._actions_needed:
- device.deactivate(self._ctx, self.nmdev)
- elif ACTION_DELETE_DEV_PROFILES in self._actions_needed:
- self.delete()
- elif ACTION_DELETE_DEV in self._actions_needed:
- device.delete_device(self._ctx, self.nmdev)
- self._next_action()
-
- def _next_action(self):
- if self._actions_needed:
- self._actions_needed.pop(0)
-
- def activate(self):
- specific_object = None
- action = (
- f"Activate profile uuid:{self.profile.get_uuid()} "
- f"id:{self.profile.get_id()}"
- )
- user_data = action, self
- self._ctx.register_async(action)
- self._ctx.client.activate_connection_async(
- self._remote_conn,
- self.nmdev,
- specific_object,
- self._ctx.cancellable,
- self._nm_profile_state.activate_connection_callback,
- user_data,
- )
-
- def delete(self):
- if self._remote_conn:
- action = (
- f"Delete profile: uuid:{self._remote_conn.get_uuid()} "
- f"id:{self._remote_conn.get_id()}"
- )
- user_data = action
- self._ctx.register_async(action, fast=True)
- self._remote_conn.delete_async(
- self._ctx.cancellable,
- self._nm_profile_state.delete_profile_callback,
- user_data,
- )
-
- def _update(self):
- flags = NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT
- if self._save_to_disk:
- flags |= NM.SettingsUpdate2Flags.TO_DISK
- else:
- flags |= NM.SettingsUpdate2Flags.IN_MEMORY
- action = (
- f"Update profile uuid:{self._remote_conn.get_uuid()} "
- f"id:{self._remote_conn.get_id()}"
- )
- user_data = action
- args = None
+ self._nm_dev = None
+ self._nm_profile = None
+ self._nm_simple_conn = None
+ self._actions = set()
+ self._activated = False
+ self._deactivated = False
+ self._profile_deleted = False
+ self._device_deleted = False
+ self._import_current()
+ self._gen_actions()
+
+ def _gen_actions(self):
+ if self._iface.is_absent:
+ self._add_action(NmProfile.ACTION_DELETE_PROFILE)
+ if self._iface.is_virtual and self._nm_dev:
+ self._add_action(NmProfile.ACTION_DELETE_DEVICE)
+ elif self._iface.is_up:
+ self._add_action(NmProfile.ACTION_MODIFIED)
+ if not self._nm_dev:
+ if self._iface.type == InterfaceType.OVS_PORT:
+ self._add_action(NmProfile.ACTION_NEW_OVS_PORT)
+ elif self._iface.type == InterfaceType.OVS_INTERFACE:
+ self._add_action(NmProfile.ACTION_NEW_OVS_IFACE)
+ elif self._iface.type == InterfaceType.VLAN:
+ self._add_action(NmProfile.ACTION_NEW_VLAN)
+ elif self._iface.type == InterfaceType.VXLAN:
+ self._add_action(NmProfile.ACTION_NEW_VXLAN)
+ else:
+ self._add_action(NmProfile.ACTION_NEW_IFACES)
- self._ctx.register_async(action, fast=True)
- self._remote_conn.update2(
- self._simple_conn.to_dbus(NM.ConnectionSerializationFlags.ALL),
- flags,
- args,
- self._ctx.cancellable,
- self._nm_profile_state.update2_callback,
- user_data,
- )
+ elif self._iface.is_down:
+ if self._nm_ac:
+ self._add_action(NmProfile.ACTION_DEACTIVATE)
+ elif self._iface.is_virtual and self._nm_dev:
+ self._add_action(NmProfile.ACTION_DELETE_DEVICE)
- def _add(self):
- nm_add_conn2_flags = NM.SettingsAddConnection2Flags
- flags = nm_add_conn2_flags.BLOCK_AUTOCONNECT
- if self._save_to_disk:
- flags |= nm_add_conn2_flags.TO_DISK
- else:
- flags |= nm_add_conn2_flags.IN_MEMORY
+ if self._iface.is_controller and self._iface.is_up:
+ if self._iface.controller:
+ self._add_action(NmProfile.ACTION_OTHER_MASTER)
+ else:
+ self._add_action(NmProfile.ACTION_TOP_MASTER)
- action = f"Add profile: {self._simple_conn.get_uuid()}"
- self._ctx.register_async(action, fast=True)
+ if (
+ self._iface.is_up
+ and self._iface.type == InterfaceType.BOND
+ and self._iface.is_bond_mode_changed
+ ):
+ # NetworkManager leaves leftover in sysfs for bond
+ # options when changing bond mode, bug:
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1819137
+ # Workaround: delete the bond interface from kernel and
+ # create again via full deactivation beforehand.
+ self._add_action(NmProfile.ACTION_DEACTIVATE_FIRST)
+
+ if self._iface.is_up and self._iface.type in (
+ InterfaceType.MAC_VLAN,
+ InterfaceType.MAC_VTAP,
+ ):
+ # NetworkManager requires the profile to be deactivated in
+ # order to modify it. Therefore if the profile is modified
+ # it needs to be deactivated beforehand in order to apply
+ # the changes and activate it again.
+ self._add_action(NmProfile.ACTION_DEACTIVATE_FIRST)
- user_data = action, self
- args = None
- ignore_out_result = False # Don't fall back to old AddConnection()
- self._ctx.client.add_connection2(
- self._simple_conn.to_dbus(NM.ConnectionSerializationFlags.ALL),
- flags,
- args,
- ignore_out_result,
- self._ctx.cancellable,
- self._nm_profile_state.add_connection2_callback,
- user_data,
+ if (
+ self._iface.is_down
+ and self._nm_dev
+ and not self._nm_dev.get_managed()
+ ):
+ # In order to deactivate an unmanaged interface, we have to
+ # activate the newly created profile to remove all kernel
+ # settings.
+ self._add_action(NmProfile.ACTION_ACTIVATE_FIRST)
+
+ def save_config(self):
+ if self._iface.is_absent or self._iface.is_down:
+ return
+
+ self._import_current()
+ 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
+ and not self._iface.is_changed
+ and set(self._iface.original_dict)
+ <= set([Interface.STATE, Interface.NAME, Interface.TYPE])
+ ):
+ cur_nm_profile = self._get_first_nm_profile()
+ if (
+ cur_nm_profile
+ and _is_memory_only(cur_nm_profile) != self._save_to_disk
+ ):
+ self._nm_profile = cur_nm_profile
+ return
+
+ # TODO: Use applied config as base profile
+ # Or even better remove the base profile argument as top level
+ # of nmstate should provide full/merged configure.
+ self._nm_simple_conn = create_new_nm_simple_conn(
+ self._iface, self._nm_profile
)
-
- def store_config(self):
+ if self._nm_profile:
+ ProfileUpdate(
+ self._ctx,
+ self._iface.name,
+ self._iface.type,
+ self._nm_simple_conn,
+ self._nm_profile,
+ self._save_to_disk,
+ ).run()
+ else:
+ ProfileAdd(
+ self._ctx,
+ self._iface.name,
+ self._iface.type,
+ self._nm_simple_conn,
+ self._save_to_disk,
+ ).run()
+
+ def _check_unsupported_memory_only(self):
if (
not self._save_to_disk
and StrictVersion(self._ctx.client.get_version())
< StrictVersion("1.28.0")
- and self.iface.type
+ and self._iface.type
in (
InterfaceType.OVS_BRIDGE,
InterfaceType.OVS_INTERFACE,
@@ -350,367 +222,344 @@ class NmProfile:
" OpenvSwitch interface."
)
- ifname = self.iface.name
- self._import_existing_profile(ifname)
+ def _check_sriov_support(self):
+ sriov_config = (
+ self._iface.to_dict()
+ .get(Ethernet.CONFIG_SUBTREE, {})
+ .get(Ethernet.SRIOV_SUBTREE)
+ )
- if self._save_to_disk:
- connections = connection.list_connections_by_ifname(
- self._ctx, ifname
- )
- for con in connections:
- if (
- not self._remote_conn
- or con.get_uuid() != self._remote_conn.get_uuid()
- ):
- nmprofile = NmProfile(self._ctx, self._save_to_disk)
- nmprofile.remote_conn = con
- nmprofile.delete()
- if not (
- set(self.original_iface_info.keys())
- <= set([Interface.STATE, Interface.NAME, Interface.TYPE])
- and self._remote_conn
- and not self._iface.is_changed
- and self.is_memory_only != self._save_to_disk
- ):
- if self.iface.state not in (
- InterfaceState.ABSENT,
- InterfaceState.DOWN,
+ if self._nm_dev and sriov_config:
+ if (
+ not self._nm_dev.props.capabilities
+ & NM.DeviceCapabilities.SRIOV
):
- settings = self._generate_connection_settings(ifname)
- self._simple_conn = connection.create_new_simple_connection(
- settings
+ raise NmstateNotSupportedError(
+ f"Interface {self._iface.name} {self._iface.type} "
+ "does not support SR-IOV"
)
- set_conn = self._simple_conn.get_setting_connection()
- set_conn.props.interface_name = ifname
- if self._remote_conn:
- self._update()
- else:
- self._add()
-
- def _generate_connection_settings(self, ifname):
- nm_iface_type = translator.Api2Nm.get_iface_type(self.iface.type)
- settings = [
- ipv4.create_setting(
- self.iface_info.get(Interface.IPV4), self._remote_conn
- ),
- ipv6.create_setting(
- self.iface_info.get(Interface.IPV6), self._remote_conn
- ),
- ]
-
- con_setting = connection.ConnectionSetting()
- if self._remote_conn:
- con_setting.import_by_profile(self._remote_conn)
- con_setting.set_profile_name(ifname)
- else:
- con_setting.create(
- con_name=ifname, iface_name=ifname, iface_type=nm_iface_type,
- )
- lldp.apply_lldp_setting(con_setting, self.iface_info)
-
- controller = self.iface_info.get(CONTROLLER_METADATA)
- controller_type = self.iface_info.get(CONTROLLER_TYPE_METADATA)
- if controller_type == LB.TYPE:
- self.iface_info[CONTROLLER_TYPE_METADATA] = bridge.BRIDGE_TYPE
- controller_type = bridge.BRIDGE_TYPE
- con_setting.set_controller(controller, controller_type)
- settings.append(con_setting.setting)
-
- # Only apply wired/ethernet configuration based on original desire
- # state rather than the merged one.
- original_state_wired = {}
- if self._iface.is_desired:
- original_state_wired = self.original_iface_info
- if self.iface.type != InterfaceType.INFINIBAND:
- # The IP over InfiniBand has its own setting for MTU and does not
- # have ethernet layer.
- wired_setting = wired.create_setting(
- original_state_wired, self._remote_conn
- )
- if wired_setting:
- settings.append(wired_setting)
+ def _activate(self):
+ if self._activated:
+ return
- user_setting = user.create_setting(self.iface_info, self._remote_conn)
- if user_setting:
- settings.append(user_setting)
+ if not self._nm_profile:
+ self._import_nm_profile_by_simple_conn()
- if self.iface.type == InterfaceType.BOND:
- settings.append(
- bond.create_setting(
- self.iface, wired_setting, self._remote_conn
- )
- )
- elif nm_iface_type == bridge.BRIDGE_TYPE:
- bridge_config = self.iface_info.get(LB.CONFIG_SUBTREE, {})
- bridge_options = bridge_config.get(LB.OPTIONS_SUBTREE)
- bridge_ports = bridge_config.get(LB.PORT_SUBTREE)
- if bridge_options or bridge_ports:
- linux_bridge_setting = bridge.create_setting(
- self.iface_info,
- self._remote_conn,
- self.original_iface_info,
- )
- settings.append(linux_bridge_setting)
- elif nm_iface_type == InterfaceType.OVS_BRIDGE:
- ovs_bridge_state = self.iface_info.get(OvsB.CONFIG_SUBTREE, {})
- ovs_bridge_options = ovs_bridge_state.get(OvsB.OPTIONS_SUBTREE)
- if ovs_bridge_options:
- settings.append(
- nm_ovs.create_bridge_setting(ovs_bridge_options)
- )
- elif nm_iface_type == InterfaceType.OVS_PORT:
- ovs_port_options = self.iface_info.get(OvsB.OPTIONS_SUBTREE)
- settings.append(nm_ovs.create_port_setting(ovs_port_options))
- elif nm_iface_type == InterfaceType.OVS_INTERFACE:
- patch_state = self.iface_info.get(
- OVSInterface.PATCH_CONFIG_SUBTREE
+ profile_activation = ProfileActivation(
+ self._ctx,
+ self._iface.name,
+ self._iface.type,
+ self._nm_profile,
+ self._nm_dev,
+ )
+ if is_activated(self._nm_ac, self._nm_dev):
+ # After ProfileUpdate(), the self._nm_profile is still hold
+ # the old settings, DeviceReapply should use the
+ # self._nm_simple_conn for updated settings.
+ DeviceReapply(
+ self._ctx,
+ self._iface.name,
+ self._iface.type,
+ self._nm_dev,
+ self._nm_simple_conn,
+ profile_activation,
+ ).run()
+ else:
+ profile_activation.run()
+ self._activated = True
+
+ def _deactivate(self):
+ if self._deactivated:
+ return
+ self._import_current()
+ if self._nm_ac:
+ ActiveConnectionDeactivate(
+ self._ctx, self._iface.name, self._iface.type, self._nm_ac
+ ).run()
+ self._deactivated = True
+
+ def _delete_profile(self):
+ if self._profile_deleted:
+ return
+ self._import_current()
+ if self._nm_profile:
+ ProfileDelete(
+ self._ctx, self._iface.name, self._iface.type, self._nm_profile
+ ).run()
+
+ self._profile_deleted = True
+
+ def _delete_device(self):
+ if self._device_deleted:
+ return
+ self._import_current()
+ if self._nm_dev:
+ DeviceDelete(
+ self._ctx, self._iface.name, self._iface.type, self._nm_dev
+ ).run()
+ self._device_deleted = True
+
+ def _add_action(self, action):
+ self._actions.add(action)
+
+ def has_action(self, action):
+ return action in self._actions
+
+ def do_action(self, action):
+ if action in (
+ NmProfile.ACTION_MODIFIED,
+ NmProfile.ACTION_ACTIVATE_FIRST,
+ NmProfile.ACTION_TOP_MASTER,
+ NmProfile.ACTION_NEW_IFACES,
+ NmProfile.ACTION_OTHER_MASTER,
+ NmProfile.ACTION_NEW_OVS_PORT,
+ NmProfile.ACTION_NEW_OVS_IFACE,
+ NmProfile.ACTION_NEW_VLAN,
+ NmProfile.ACTION_NEW_VXLAN,
+ ):
+ self._activate()
+ elif (
+ action
+ in (
+ NmProfile.ACTION_DELETE_PROFILE,
+ NmProfile.ACTION_DELETE_DEVICE,
+ NmProfile.ACTION_DEACTIVATE,
+ NmProfile.ACTION_DEACTIVATE_FIRST,
)
- settings.extend(nm_ovs.create_interface_setting(patch_state))
- elif self.iface.type == InterfaceType.INFINIBAND:
- ib_setting = create_infiniband_setting(
- self.iface_info, self._remote_conn, self.original_iface_info,
+ and not self._deactivated
+ ):
+ self._deactivate()
+ elif action == NmProfile.ACTION_DELETE_PROFILE:
+ self._delete_profile()
+ elif action == NmProfile.ACTION_DELETE_DEVICE:
+ self._delete_device()
+ else:
+ raise NmstateInternalError(
+ f"BUG: NmProfile.do_action() got unknown action {action}"
)
- if ib_setting:
- settings.append(ib_setting)
- bridge_port_options = self.iface_info.get(
- BridgeIface.BRPORT_OPTIONS_METADATA
+ def _import_current(self):
+ self._nm_dev = get_nm_dev(
+ self._ctx, self._iface.name, self._iface.type
+ )
+ self._nm_ac = (
+ self._nm_dev.get_active_connection() if self._nm_dev else None
+ )
+ self._nm_profile = (
+ self._nm_ac.get_connection() if self._nm_ac else None
)
- if bridge_port_options and controller_type == bridge.BRIDGE_TYPE:
- settings.append(
- bridge.create_port_setting(
- bridge_port_options, self._remote_conn
+
+ def _import_nm_profile_by_simple_conn(self):
+ self._ctx.refresh_content()
+ for nm_profile in self._ctx.client.get_connections():
+ if nm_profile.get_uuid() == self._nm_simple_conn.get_uuid():
+ self._nm_profile = nm_profile
+
+ def _get_first_nm_profile(self):
+ for nm_profile in self._ctx.client.get_connections():
+ if nm_profile.get_interface_name() == self._iface.name and (
+ self._nm_iface_type is None
+ or nm_profile.get_connection_type() == self._nm_iface_type
+ ):
+ return nm_profile
+ return None
+
+ def delete_other_profiles(self):
+ """
+ Remove all profiles except the NM.RemoteConnection used by current
+ NM.ActiveConnection if interface is marked as UP
+ """
+ if self._iface.is_down:
+ return
+ self._import_current()
+ for nm_profile in self._ctx.client.get_connections():
+ if (
+ nm_profile.get_interface_name() == self._iface.name
+ and (
+ self._nm_iface_type is None
+ or nm_profile.get_connection_type() == self._nm_iface_type
)
- )
+ and (
+ self._nm_profile is None
+ or nm_profile.get_uuid() != self._nm_profile.get_uuid()
+ )
+ ):
+ ProfileDelete(
+ self._ctx, self._iface.name, self._iface.type, nm_profile
+ ).run()
+
+
+class ProfileAdd:
+ def __init__(
+ self, ctx, iface_name, iface_type, nm_simple_conn, save_to_disk
+ ):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_simple_conn = nm_simple_conn
+ self._save_to_disk = save_to_disk
- vlan_setting = vlan.create_setting(self.iface_info, self._remote_conn)
- if vlan_setting:
- settings.append(vlan_setting)
+ def run(self):
+ nm_add_conn2_flags = NM.SettingsAddConnection2Flags
+ flags = nm_add_conn2_flags.BLOCK_AUTOCONNECT
+ if self._save_to_disk:
+ flags |= nm_add_conn2_flags.TO_DISK
+ else:
+ flags |= nm_add_conn2_flags.IN_MEMORY
- vxlan_setting = vxlan.create_setting(
- self.iface_info, self._remote_conn
+ action = (
+ f"Add profile: {self._nm_simple_conn.get_uuid()}, "
+ f"iface:{self._iface_name}, type:{self._iface_type}"
)
- if vxlan_setting:
- settings.append(vxlan_setting)
+ self._ctx.register_async(action, fast=True)
- sriov_setting = sriov.create_setting(
- self._ctx, self.iface_info, self._remote_conn
+ user_data = action
+ args = None
+ ignore_out_result = False # Don't fall back to old AddConnection()
+ self._ctx.client.add_connection2(
+ self._nm_simple_conn.to_dbus(NM.ConnectionSerializationFlags.ALL),
+ flags,
+ args,
+ ignore_out_result,
+ self._ctx.cancellable,
+ self._add_profile_callback,
+ user_data,
)
- if sriov_setting:
- settings.append(sriov_setting)
- team_setting = team.create_setting(self.iface_info, self._remote_conn)
- if team_setting:
- settings.append(team_setting)
+ def _add_profile_callback(self, nm_client, result, user_data):
+ action = user_data
+ if self._ctx.is_cancelled():
+ return
+ try:
+ nm_profile = nm_client.add_connection2_finish(result)[0]
+ except Exception as e:
+ self._ctx.fail(
+ NmstateLibnmError(f"{action} failed with error: {e}")
+ )
+ return
- if VRF.CONFIG_SUBTREE in self.iface_info:
- settings.append(
- create_vrf_setting(self.iface_info[VRF.CONFIG_SUBTREE])
+ if nm_profile is None:
+ self._ctx.fail(
+ NmstateLibnmError(
+ f"{action} failed with error: 'None returned from "
+ "NM.Client.add_connection2_finish()'"
+ )
)
+ else:
+ self._ctx.finish_async(action)
+
+
+class ProfileUpdate:
+ def __init__(
+ self,
+ ctx,
+ iface_name,
+ iface_type,
+ nm_simple_conn,
+ nm_profile,
+ save_to_disk,
+ ):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_simple_conn = nm_simple_conn
+ self._nm_profile = nm_profile
+ self._save_to_disk = save_to_disk
- macvlan_setting = macvlan.create_setting(
- self.iface_info, self._remote_conn
+ def run(self):
+ flags = NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT
+ if self._save_to_disk:
+ flags |= NM.SettingsUpdate2Flags.TO_DISK
+ else:
+ flags |= NM.SettingsUpdate2Flags.IN_MEMORY
+ action = (
+ f"Update profile uuid:{self._nm_profile.get_uuid()} "
+ f"iface:{self._iface_name} type:{self._iface_type}"
)
- if macvlan_setting:
- settings.append(macvlan_setting)
+ user_data = action
+ args = None
- return settings
+ self._ctx.register_async(action, fast=True)
+ self._nm_profile.update2(
+ self._nm_simple_conn.to_dbus(NM.ConnectionSerializationFlags.ALL),
+ flags,
+ args,
+ self._ctx.cancellable,
+ self._update_profile_callback,
+ user_data,
+ )
- def _import_existing_profile(self, ifname):
- self._nmdev = self._ctx.get_nm_dev(ifname)
- if self._nmdev:
- self._remote_conn = self._import_remote_conn_by_device()
+ def _update_profile_callback(self, nm_profile, result, user_data):
+ action = user_data
+ if self._ctx.is_cancelled():
+ return
+ try:
+ ret = nm_profile.update2_finish(result)
+ except Exception as e:
+ self._ctx.fail(
+ NmstateLibnmError(f"{action} failed with error={e}")
+ )
+ return
+
+ if ret is None:
+ self._ctx.fail(
+ NmstateLibnmError(
+ f"{action} failed with error='None returned from "
+ "update2_finish()'"
+ )
+ )
else:
- # Profile for virtual interface does not have a NM.Device
- # associated.
- self._remote_conn = self._ctx.client.get_connection_by_id(ifname)
+ self._ctx.finish_async(action)
- def _import_remote_conn_by_device(self):
- act_conn = self._nmdev.get_active_connection()
- if act_conn:
- self._nm_ac = act_conn
- return act_conn.get_connection()
- return None
+class ProfileDelete:
+ def __init__(self, ctx, iface_name, iface_type, nm_profile):
+ self._ctx = ctx
+ self._iface_name = iface_name
+ self._iface_type = iface_type
+ self._nm_profile = nm_profile
- def classify_profile_for_actions(self, groups):
- if not self.nmdev:
- if self.iface.state == InterfaceState.UP:
- self._actions_needed.append(ACTION_ACTIVATE)
- if (
- self.iface.type in CONTROLLER_IFACE_TYPES
- and not self.iface_info.get(CONTROLLER_METADATA)
- ):
- groups["new_controller_not_as_port"].add(self)
- elif self.iface.type == InterfaceType.OVS_INTERFACE:
- groups["new_ovs_interface_to_activate"].add(self)
- elif self.iface.type == InterfaceType.OVS_PORT:
- groups["new_ovs_port_to_activate"].add(self)
- elif self.iface.type in (
- InterfaceType.VLAN,
- InterfaceType.VXLAN,
- ):
- groups["new_vlan_x_to_activate"].add(self)
- else:
- groups["new_ifaces_to_activate"].add(self)
- elif self.iface.state == InterfaceState.ABSENT:
- # Delete absent profiles
- self._actions_needed.append(ACTION_DELETE_PROFILE)
- groups["profiles_to_delete"].add(self)
+ def run(self):
+ action = (
+ f"Delete profile: uuid:{self._nm_profile.get_uuid()} "
+ f"id:{self._nm_profile.get_id()} "
+ f"iface:{self._iface_name} type:{self._iface_type}"
+ )
+ user_data = action
+ self._ctx.register_async(action, fast=True)
+ self._nm_profile.delete_async(
+ self._ctx.cancellable,
+ self._delete_profile_callback,
+ user_data,
+ )
+
+ def _delete_profile_callback(self, nm_profile, result, user_data):
+ action = user_data
+ if self._ctx.is_cancelled():
+ return
+ try:
+ success = nm_profile.delete_finish(result)
+ except Exception as e:
+ self._ctx.fail(NmstateLibnmError(f"{action} failed: error={e}"))
+ return
+
+ if success:
+ self._ctx.finish_async(action)
else:
- if not self.nmdev.get_managed():
- mark_device_as_managed(self._ctx, self.nmdev)
- if self.iface.state == InterfaceState.UP:
- if self.iface.type == InterfaceType.BOND:
- iface = BondIface(self.iface_info)
- # NetworkManager leaves leftover in sysfs for bond
- # options when changing bond mode, bug:
- # https://bugzilla.redhat.com/show_bug.cgi?id=1819137
- # Workaround: delete the bond interface from kernel and
- # create again via full deactivation beforehand.
- if iface.is_bond_mode_changed:
- logging.debug(
- f"Bond interface {self.iface.name} is changing "
- "bond mode, will do full deactivation before "
- "applying changes"
- )
- self._actions_needed.append(
- ACTION_DEACTIVATE_BEFOREHAND
- )
- groups["profiles_to_deactivate_beforehand"].add(self)
- elif self.iface.type == InterfaceType.MAC_VLAN:
- self._actions_needed.append(ACTION_DEACTIVATE_BEFOREHAND)
- groups["profiles_to_deactivate_beforehand"].add(self)
- self._actions_needed.append(ACTION_MODIFY)
- if self.iface.type in CONTROLLER_IFACE_TYPES:
- groups["controller_ifaces_to_edit"].add(self)
- else:
- groups["ifaces_to_edit"].add(self)
- elif self.iface.state in (
- InterfaceState.DOWN,
- InterfaceState.ABSENT,
- ):
- is_absent = self.iface.state == InterfaceState.ABSENT
- self._actions_needed.append(ACTION_DEACTIVATE)
- groups["profiles_to_deactivate"].add(self)
- if is_absent:
- self._actions_needed.append(ACTION_DELETE_DEV_PROFILES)
- groups["devs_to_delete_profile"].add(self)
- if (
- is_absent
- and self.nmdev.is_software()
- and self.nmdev.get_device_type() != NM.DeviceType.VETH
- ):
- self._actions_needed.append(ACTION_DELETE_DEV)
- groups["devs_to_delete"].add(self)
- else:
- raise NmstateValueError(
- "Invalid state {} for interface {}".format(
- self.iface.state, self.iface.name,
- )
+ self._ctx.fail(
+ NmstateLibnmError(
+ f"{action} failed: error='None returned from "
+ "delete_finish'"
)
+ )
-def get_all_applied_configs(context):
- applied_configs = {}
- for nm_dev in list_devices(context.client):
- if (
- nm_dev.get_state()
- in (NM.DeviceState.ACTIVATED, NM.DeviceState.IP_CONFIG,)
- and nm_dev.get_managed()
- ):
- iface_name = nm_dev.get_iface()
- if iface_name:
- iface_type_str = nm_dev.get_type_description()
- action = (
- f"Retrieve applied config: {iface_type_str} {iface_name}"
- )
- context.register_async(action, fast=True)
- nm_dev.get_applied_connection_async(
- flags=0,
- cancellable=context.cancellable,
- callback=profile_state.get_applied_config_callback,
- user_data=(iface_name, action, applied_configs, context),
- )
- context.wait_all_finish()
- return applied_configs
-
-
-def _preapply_dns_fix_for_profiles(context, net_state):
- """
- * When DNS configuration does not changed and old interface hold DNS
- configuration is not included in `ifaces_desired_state`, preserve
- the old DNS configure by removing DNS metadata from
- `ifaces_desired_state`.
- * When DNS configuration changed, include old interface which is holding
- DNS configuration, so it's DNS configure could be removed.
- """
- cur_dns_iface_names = nm_dns.get_dns_config_iface_names(
- ipv4.acs_and_ip_profiles(context.client),
- ipv6.acs_and_ip_profiles(context.client),
- )
-
- # Whether to mark interface as changed which is used for holding old DNS
- # configurations
- remove_existing_dns_config = False
- # Whether to preserve old DNS config by DNS metadata to be removed from
- # desired state
- preserve_old_dns_config = False
- if net_state.dns.config == net_state.dns.current_config:
- for cur_dns_iface_name in cur_dns_iface_names:
- iface = net_state.ifaces[cur_dns_iface_name]
- if iface.is_changed or iface.is_desired:
- remove_existing_dns_config = True
- if not remove_existing_dns_config:
- preserve_old_dns_config = True
- else:
- remove_existing_dns_config = True
-
- if remove_existing_dns_config:
- for cur_dns_iface_name in cur_dns_iface_names:
- iface = net_state.ifaces[cur_dns_iface_name]
- iface.mark_as_changed()
-
- if preserve_old_dns_config:
- for iface in net_state.ifaces.values():
- if iface.is_changed or iface.is_desired:
- iface.remove_dns_metadata()
-
-
-def _mark_nm_external_subordinate_changed(context, net_state):
- """
- When certain main interface contains subordinates is marked as
- connected(externally), it means its profile is memory only and will lost
- on next deactivation.
- For this case, we should mark the subordinate as changed.
- that subordinate should be marked as changed for NM to take over.
- """
- for iface in net_state.ifaces.values():
- if iface.type in CONTROLLER_IFACE_TYPES:
- for subordinate in iface.port:
- nmdev = context.get_nm_dev(subordinate)
- if nmdev:
- if is_externally_managed(nmdev):
- subordinate_iface = net_state.ifaces.get(subordinate)
- if subordinate_iface:
- subordinate_iface.mark_as_changed()
-
-
-def _mark_mode_changed_bond_child_interface_as_changed(net_state):
- """
- When bond mode changed, due to NetworkManager bug
- https://bugzilla.redhat.com/show_bug.cgi?id=1881318
- the bond child will be deactivated.
- This is workaround would be manually activate the childs.
- """
- for iface in net_state.ifaces.values():
- if not iface.parent:
- continue
- parent_iface = net_state.ifaces[iface.parent]
- if (
- parent_iface.is_up
- and parent_iface.type == InterfaceType.BOND
- and parent_iface.is_bond_mode_changed
- ):
- iface.mark_as_changed()
+def _is_memory_only(nm_profile):
+ if nm_profile:
+ profile_flags = nm_profile.get_flags()
+ return (
+ NM.SettingsConnectionFlags.UNSAVED & profile_flags
+ or NM.SettingsConnectionFlags.VOLATILE & profile_flags
+ )
+ return False
diff --git a/libnmstate/nm/profiles.py b/libnmstate/nm/profiles.py
new file mode 100644
index 0000000..8cf1907
--- /dev/null
+++ b/libnmstate/nm/profiles.py
@@ -0,0 +1,265 @@
+#
+# 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 .
+#
+
+# This file is targeting:
+# * Actions required the knownldege of multiple NmProfile
+
+import logging
+
+from libnmstate.schema import InterfaceType
+
+from .common import NM
+from .device import is_externally_managed
+from .device import list_devices
+from .device import get_nm_dev
+from .dns import get_dns_config_iface_names
+from .ipv4 import acs_and_ip_profiles as acs_and_ip4_profiles
+from .ipv6 import acs_and_ip_profiles as acs_and_ip6_profiles
+from .ovs import create_iface_for_nm_ovs_port
+from .profile import NmProfile
+from .profile import ProfileDelete
+
+
+class NmProfiles:
+ def __init__(self, context):
+ self._ctx = context
+
+ def apply_config(self, net_state, save_to_disk):
+ self._prepare_state_for_profiles(net_state)
+ self._profiles = [
+ NmProfile(self._ctx, iface, save_to_disk)
+ for iface in net_state.ifaces.all_ifaces()
+ if (iface.is_changed or iface.is_desired) and not iface.is_ignore
+ ]
+
+ for profile in self._profiles:
+ profile.save_config()
+ self._ctx.wait_all_finish()
+
+ for action in NmProfile.ACTIONS:
+ for profile in self._profiles:
+ if profile.has_action(action):
+ profile.do_action(action)
+ self._ctx.wait_all_finish()
+
+ if save_to_disk:
+ for profile in self._profiles:
+ profile.delete_other_profiles()
+ _delete_orphan_nm_ovs_port_profiles(self._ctx, net_state)
+
+ def _prepare_state_for_profiles(self, net_state):
+ _preapply_dns_fix_for_profiles(self._ctx, net_state)
+ _mark_nm_external_subordinate_changed(self._ctx, net_state)
+ _mark_mode_changed_bond_child_interface_as_changed(net_state)
+ _append_nm_ovs_port_iface(net_state)
+
+
+def _append_nm_ovs_port_iface(net_state):
+ """
+ In NM OVS, each OVS internal/system/ interface should be
+ subordinate of NM OVS port profile which is port of the OVS bridge
+ profile.
+ We need to create/delete this NM OVS port profile accordingly.
+ """
+ nm_ovs_port_ifaces = {}
+
+ for iface in net_state.ifaces.all_kernel_ifaces.values():
+ if iface.controller_type == InterfaceType.OVS_BRIDGE:
+ nm_ovs_port_iface = create_iface_for_nm_ovs_port(iface)
+ iface.set_controller(
+ nm_ovs_port_iface.name, InterfaceType.OVS_PORT
+ )
+ if iface.is_desired or iface.is_changed:
+ nm_ovs_port_iface.mark_as_changed()
+ nm_ovs_port_ifaces[nm_ovs_port_iface.name] = nm_ovs_port_iface
+
+ net_state.ifaces.add_ifaces(nm_ovs_port_ifaces.values())
+
+
+def get_all_applied_configs(context):
+ applied_configs = {}
+ for nm_dev in list_devices(context.client):
+ if (
+ nm_dev.get_state()
+ in (
+ NM.DeviceState.ACTIVATED,
+ NM.DeviceState.IP_CONFIG,
+ )
+ and nm_dev.get_managed()
+ ):
+ iface_name = nm_dev.get_iface()
+ if iface_name:
+ iface_type_str = nm_dev.get_type_description()
+ action = (
+ f"Retrieve applied config: {iface_type_str} {iface_name}"
+ )
+ context.register_async(action, fast=True)
+ nm_dev.get_applied_connection_async(
+ flags=0,
+ cancellable=context.cancellable,
+ callback=_get_applied_config_callback,
+ user_data=(iface_name, action, applied_configs, context),
+ )
+ context.wait_all_finish()
+ return applied_configs
+
+
+def _get_applied_config_callback(nm_dev, result, user_data):
+ iface_name, action, applied_configs, context = user_data
+ context.finish_async(action)
+ try:
+ remote_conn, _ = nm_dev.get_applied_connection_finish(result)
+ # TODO: We should use both interface name and type as key below.
+ applied_configs[nm_dev.get_iface()] = remote_conn
+ except Exception as e:
+ logging.warning(
+ "Failed to retrieve applied config for device "
+ f"{iface_name}: {e}"
+ )
+
+
+def _preapply_dns_fix_for_profiles(context, net_state):
+ """
+ * When DNS configuration does not changed and old interface hold DNS
+ configuration is not included in `ifaces_desired_state`, preserve
+ the old DNS configure by removing DNS metadata from
+ `ifaces_desired_state`.
+ * When DNS configuration changed, include old interface which is holding
+ DNS configuration, so it's DNS configure could be removed.
+ """
+ cur_dns_iface_names = get_dns_config_iface_names(
+ acs_and_ip4_profiles(context.client),
+ acs_and_ip6_profiles(context.client),
+ )
+
+ # Whether to mark interface as changed which is used for holding old DNS
+ # configurations
+ remove_existing_dns_config = False
+ # Whether to preserve old DNS config by DNS metadata to be removed from
+ # desired state
+ preserve_old_dns_config = False
+ if net_state.dns.config == net_state.dns.current_config:
+ for cur_dns_iface_name in cur_dns_iface_names:
+ iface = net_state.ifaces.all_kernel_ifaces[cur_dns_iface_name]
+ if iface.is_changed or iface.is_desired:
+ remove_existing_dns_config = True
+ if not remove_existing_dns_config:
+ preserve_old_dns_config = True
+ else:
+ remove_existing_dns_config = True
+
+ if remove_existing_dns_config:
+ for cur_dns_iface_name in cur_dns_iface_names:
+ iface = net_state.ifaces.all_kernel_ifaces[cur_dns_iface_name]
+ iface.mark_as_changed()
+
+ if preserve_old_dns_config:
+ for iface in net_state.ifaces.all_kernel_ifaces.values():
+ if iface.is_changed or iface.is_desired:
+ iface.remove_dns_metadata()
+
+
+def _mark_nm_external_subordinate_changed(context, net_state):
+ """
+ When certain main interface contains subordinates is marked as
+ connected(externally), it means its profile is memory only and will lost
+ on next deactivation.
+ For this case, we should mark the subordinate as changed.
+ that subordinate should be marked as changed for NM to take over.
+ """
+ for iface in net_state.ifaces.all_ifaces():
+ if (
+ iface.is_controller
+ and iface.is_up
+ and (iface.is_changed or iface.is_desired)
+ ):
+ for subordinate in iface.port:
+ port_iface = net_state.ifaces.all_kernel_ifaces.get(
+ subordinate
+ )
+ if port_iface:
+ nmdev = get_nm_dev(context, subordinate, port_iface.type)
+ if nmdev:
+ if is_externally_managed(nmdev):
+ port_iface.mark_as_changed()
+
+
+def _mark_mode_changed_bond_child_interface_as_changed(net_state):
+ """
+ When bond mode changed, due to NetworkManager bug
+ https://bugzilla.redhat.com/show_bug.cgi?id=1881318
+ the bond child will be deactivated.
+ This is workaround would be manually activate the childs.
+ """
+ for iface in net_state.ifaces.all_kernel_ifaces.values():
+ if not iface.parent:
+ continue
+ parent_iface = net_state.ifaces.get_iface(
+ iface.parent, InterfaceType.BOND
+ )
+ if (
+ parent_iface
+ and parent_iface.is_up
+ and parent_iface.is_bond_mode_changed
+ ):
+ iface.mark_as_changed()
+
+
+def _delete_orphan_nm_ovs_port_profiles(context, net_state):
+ all_deleted_ovs_bridges = {}
+ for iface in net_state.ifaces.all_user_space_ifaces:
+ if iface.type == InterfaceType.OVS_BRIDGE and iface.is_absent:
+ all_deleted_ovs_bridges[iface.name] = iface
+ if not all_deleted_ovs_bridges:
+ return
+ for nm_profile in context.client.get_connections():
+ if nm_profile.get_connection_type() != InterfaceType.OVS_PORT:
+ continue
+ conn_setting = nm_profile.get_setting_connection()
+ if not conn_setting:
+ continue
+ ovs_port_name = nm_profile.get_interface_name()
+ controller = conn_setting.get_master()
+ ovs_br_iface = all_deleted_ovs_bridges.get(controller)
+ need_delete = False
+ if ovs_br_iface:
+ if ovs_br_iface.is_absent:
+ need_delete = True
+ else:
+ has_ovs_interface = False
+ for port in ovs_br_iface.port:
+ ovs_iface = net_state.ifaces.all_kernel_ifaces.get(port)
+ if (
+ ovs_iface
+ and ovs_iface.controller == ovs_port_name
+ and ovs_iface.controller_type == InterfaceType.OVS_PORT
+ ):
+ has_ovs_interface = True
+ break
+ if not has_ovs_interface:
+ need_delete = True
+ if need_delete:
+ ProfileDelete(
+ context,
+ ovs_port_name,
+ InterfaceType.OVS_PORT,
+ nm_profile,
+ ).run()
+
+ context.wait_all_finish()
diff --git a/libnmstate/nm/route.py b/libnmstate/nm/route.py
index 113f2aa..cb43f28 100644
--- a/libnmstate/nm/route.py
+++ b/libnmstate/nm/route.py
@@ -160,44 +160,6 @@ def get_static_gateway_iface(family, iface_routes):
return None
-def get_routing_rule_config(acs_and_ip_profiles):
- rules = []
- for (_, ip_profile) in acs_and_ip_profiles:
- for i in range(ip_profile.get_num_routing_rules()):
- nm_rule = ip_profile.get_routing_rule(i)
- rules.append(_nm_rule_to_info(nm_rule))
-
- return rules
-
-
-def _nm_rule_to_info(nm_rule):
- info = {
- RouteRule.IP_FROM: _nm_rule_get_from(nm_rule),
- RouteRule.IP_TO: _nm_rule_get_to(nm_rule),
- RouteRule.PRIORITY: nm_rule.get_priority(),
- RouteRule.ROUTE_TABLE: nm_rule.get_table(),
- }
- cleanup_keys = [key for key, val in info.items() if val is None]
- for key in cleanup_keys:
- del info[key]
-
- return info
-
-
-def _nm_rule_get_from(nm_rule):
- if nm_rule.get_from():
- return iplib.to_ip_address_full(
- nm_rule.get_from(), nm_rule.get_from_len()
- )
- return None
-
-
-def _nm_rule_get_to(nm_rule):
- if nm_rule.get_to():
- return iplib.to_ip_address_full(nm_rule.get_to(), nm_rule.get_to_len())
- return None
-
-
def add_route_rules(setting_ip, family, rules):
for rule in rules:
setting_ip.add_routing_rule(_rule_info_to_nm_rule(rule, family))
diff --git a/libnmstate/nm/sriov.py b/libnmstate/nm/sriov.py
index 5213bef..a7e387c 100644
--- a/libnmstate/nm/sriov.py
+++ b/libnmstate/nm/sriov.py
@@ -17,9 +17,7 @@
# along with this program. If not, see .
#
-from libnmstate.error import NmstateNotSupportedError
from libnmstate.schema import Ethernet
-from libnmstate.schema import Interface
from .common import NM
from .common import GLib
@@ -49,9 +47,8 @@ SRIOV_NMSTATE_TO_NM_MAP = {
}
-def create_setting(context, iface_state, base_con_profile):
+def create_setting(iface_state, base_con_profile):
sriov_setting = None
- ifname = iface_state[Interface.NAME]
sriov_config = iface_state.get(Ethernet.CONFIG_SUBTREE, {}).get(
Ethernet.SRIOV_SUBTREE
)
@@ -61,11 +58,6 @@ def create_setting(context, iface_state, base_con_profile):
NM.SETTING_SRIOV_SETTING_NAME
)
if sriov_config:
- if not _has_sriov_capability(context, ifname):
- raise NmstateNotSupportedError(
- f"Interface '{ifname}' does not support SR-IOV"
- )
-
if sriov_setting:
sriov_setting = sriov_setting.duplicate()
else:
@@ -117,8 +109,3 @@ def _set_nm_attribute(vf_object, key, value):
def _remove_sriov_vfs_in_setting(vfs_config, sriov_setting, vf_ids_to_remove):
for vf_id in vf_ids_to_remove:
yield vf_id
-
-
-def _has_sriov_capability(context, ifname):
- dev = context.get_nm_dev(ifname)
- return dev and (NM.DeviceCapabilities.SRIOV & dev.props.capabilities)
diff --git a/libnmstate/nm/team.py b/libnmstate/nm/team.py
index 5630018..cf96d73 100644
--- a/libnmstate/nm/team.py
+++ b/libnmstate/nm/team.py
@@ -63,12 +63,12 @@ def _convert_team_config_to_teamd_format(team_config, ifname):
team_config = copy.deepcopy(team_config)
team_config[TEAMD_JSON_DEVICE] = ifname
- team_ports = team_config.get(Team.PORT_SUBTREE, ())
+ team_ports = team_config.pop(Team.PORT_SUBTREE, ())
team_ports_formatted = {
port[Team.Port.NAME]: _dict_key_filter(port, Team.Port.NAME)
for port in team_ports
}
- team_config[Team.PORT_SUBTREE] = team_ports_formatted
+ team_config[TEAMD_JSON_PORTS] = team_ports_formatted
return team_config
diff --git a/libnmstate/nm/translator.py b/libnmstate/nm/translator.py
index 992b2a3..c3993ab 100644
--- a/libnmstate/nm/translator.py
+++ b/libnmstate/nm/translator.py
@@ -49,6 +49,7 @@ class Api2Nm:
InterfaceType.LINUX_BRIDGE: NM.SETTING_BRIDGE_SETTING_NAME,
InterfaceType.VRF: NM.SETTING_VRF_SETTING_NAME,
InterfaceType.INFINIBAND: NM.SETTING_INFINIBAND_SETTING_NAME,
+ InterfaceType.MAC_VTAP: NM.SETTING_MACVLAN_SETTING_NAME,
InterfaceType.MAC_VLAN: NM.SETTING_MACVLAN_SETTING_NAME,
}
try:
diff --git a/libnmstate/nmstate.py b/libnmstate/nmstate.py
index 302f2cd..a7373c9 100644
--- a/libnmstate/nmstate.py
+++ b/libnmstate/nmstate.py
@@ -72,11 +72,7 @@ def show_with_plugins(plugins, include_status_data=None):
report[Route.KEY] = _get_routes_from_plugins(plugins)
- route_rule_plugin = _find_plugin_for_capability(
- plugins, NmstatePlugin.PLUGIN_CAPABILITY_ROUTE_RULE
- )
- if route_rule_plugin:
- report[RouteRule.KEY] = route_rule_plugin.get_route_rules()
+ report[RouteRule.KEY] = _get_route_rules_from_plugins(plugins)
dns_plugin = _find_plugin_for_capability(
plugins, NmstatePlugin.PLUGIN_CAPABILITY_DNS
@@ -315,6 +311,20 @@ def _get_routes_from_plugins(plugins):
return ret
+def _get_route_rules_from_plugins(plugins):
+ ret = {RouteRule.CONFIG: []}
+ for plugin in plugins:
+ if (
+ NmstatePlugin.PLUGIN_CAPABILITY_ROUTE_RULE
+ in plugin.plugin_capabilities
+ ):
+ plugin_route_rules = plugin.get_route_rules()
+ ret[RouteRule.CONFIG].extend(
+ plugin_route_rules.get(RouteRule.CONFIG, [])
+ )
+ return ret
+
+
def _get_iface_types_by_name(iface_infos, name):
"""
Return the type of all ifaces with valid type and specified name
diff --git a/libnmstate/plugins/nmstate_plugin_ovsdb.py b/libnmstate/plugins/nmstate_plugin_ovsdb.py
index ee87f71..e9106ef 100644
--- a/libnmstate/plugins/nmstate_plugin_ovsdb.py
+++ b/libnmstate/plugins/nmstate_plugin_ovsdb.py
@@ -193,7 +193,7 @@ class NmstateOvsdbPlugin(NmstatePlugin):
][OvsDB.EXTERNAL_IDS]
pending_changes = []
- for iface in net_state.ifaces.values():
+ for iface in net_state.ifaces.all_ifaces():
if not iface.is_changed and not iface.is_desired:
continue
if not iface.is_up:
diff --git a/libnmstate/route.py b/libnmstate/route.py
index 8664d8c..611ee91 100644
--- a/libnmstate/route.py
+++ b/libnmstate/route.py
@@ -28,6 +28,7 @@ from libnmstate.prettystate import format_desired_current_state_diff
from libnmstate.schema import Interface
from libnmstate.schema import Route
+from .ifaces.base_iface import BaseIface
from .state import StateEntry
from .state import state_match
@@ -121,7 +122,7 @@ class RouteEntry(StateEntry):
if not self.destination:
self._invalid_reason = "Route entry does not have destination"
return False
- iface = ifaces.get(self.next_hop_interface)
+ iface = ifaces.all_kernel_ifaces.get(self.next_hop_interface)
if not iface:
self._invalid_reason = (
f"Route {self.to_dict()} next hop to unknown interface"
@@ -190,7 +191,9 @@ class RouteState:
rt = RouteEntry(entry)
if not rt.absent:
if rt.is_valid(ifaces):
- ifaces[rt.next_hop_interface].mark_as_changed()
+ ifaces.all_kernel_ifaces[
+ rt.next_hop_interface
+ ].mark_as_changed()
self._routes[rt.next_hop_interface].add(rt)
else:
raise NmstateValueError(rt.invalid_reason)
@@ -209,7 +212,6 @@ class RouteState:
if not rt.match(route):
new_routes.add(route)
if new_routes != route_set:
- ifaces[iface_name].mark_as_changed()
self._routes[iface_name] = new_routes
def gen_metadata(self, ifaces):
@@ -229,6 +231,10 @@ class RouteState:
Interface.IPV4: [],
Interface.IPV6: [],
}
+ if route_set != self._cur_routes[iface_name]:
+ route_metadata[iface_name][
+ BaseIface.ROUTE_CHANGED_METADATA
+ ] = True
for route in route_set:
family = Interface.IPV6 if route.is_ipv6 else Interface.IPV4
route_metadata[iface_name][family].append(route.to_dict())
diff --git a/libnmstate/route_rule.py b/libnmstate/route_rule.py
index cadbc73..1a5487b 100644
--- a/libnmstate/route_rule.py
+++ b/libnmstate/route_rule.py
@@ -1,7 +1,25 @@
+#
+# 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 .
+#
+
from collections import defaultdict
import logging
-from libnmstate.error import NmstateNotImplementedError
from libnmstate.error import NmstateVerificationError
from libnmstate.error import NmstateValueError
from libnmstate.iplib import KERNEL_MAIN_ROUTE_TABLE_ID
@@ -13,6 +31,7 @@ from libnmstate.schema import InterfaceIP
from libnmstate.schema import RouteRule
from libnmstate.schema import Route
+from .ifaces.base_iface import BaseIface
from .state import StateEntry
from .state import state_match
@@ -23,21 +42,23 @@ class RouteRuleEntry(StateEntry):
self.ip_to = route_rule.get(RouteRule.IP_TO)
self.priority = route_rule.get(RouteRule.PRIORITY)
self.route_table = route_rule.get(RouteRule.ROUTE_TABLE)
+ self.state = route_rule.get(RouteRule.STATE)
self._complement_defaults()
self._canonicalize_ip_network()
def _complement_defaults(self):
- if self.ip_from is None:
- self.ip_from = ""
- if self.ip_to is None:
- self.ip_to = ""
- if self.priority is None:
- self.priority = RouteRule.USE_DEFAULT_PRIORITY
- if (
- self.route_table is None
- or self.route_table == RouteRule.USE_DEFAULT_ROUTE_TABLE
- ):
- self.route_table = KERNEL_MAIN_ROUTE_TABLE_ID
+ if not self.absent:
+ if self.ip_from is None:
+ self.ip_from = ""
+ if self.ip_to is None:
+ self.ip_to = ""
+ if self.priority is None:
+ self.priority = RouteRule.USE_DEFAULT_PRIORITY
+ if (
+ self.route_table is None
+ or self.route_table == RouteRule.USE_DEFAULT_ROUTE_TABLE
+ ):
+ self.route_table = KERNEL_MAIN_ROUTE_TABLE_ID
def _canonicalize_ip_network(self):
if self.ip_from:
@@ -63,9 +84,7 @@ class RouteRuleEntry(StateEntry):
@property
def absent(self):
- raise NmstateNotImplementedError(
- "RouteRuleEntry does not support absent property"
- )
+ return self.state == RouteRule.STATE_ABSENT
def is_valid(self, config_iface_routes):
"""
@@ -89,28 +108,50 @@ class RouteRuleState:
self._cur_rules = defaultdict(set)
self._rules = defaultdict(set)
if cur_rule_state:
- for rule_dict in _get_config(cur_rule_state):
- rule = RouteRuleEntry(rule_dict)
- self._cur_rules[rule.route_table].add(rule)
+ for entry in _get_config(cur_rule_state):
+ rl = RouteRuleEntry(entry)
+ self._cur_rules[rl.route_table].add(rl)
+ if not route_state or rl.is_valid(
+ route_state.config_iface_routes
+ ):
+ self._rules[rl.route_table].add(rl)
if des_rule_state:
- for rule_dict in _get_config(des_rule_state):
- rule = RouteRuleEntry(rule_dict)
- self._rules[rule.route_table].add(rule)
- if self._rules != self._cur_rules:
- self._config_changed = True
- else:
- # Discard invalid route rule when merging from current
- for rules in self._cur_rules.values():
- for rule in rules:
- if not route_state or rule.is_valid(
- route_state.config_iface_routes
- ):
- self._rules[rule.route_table].add(rule)
+ self._merge_rules(des_rule_state, route_state)
@property
def _config(self):
return _get_config(self._rules)
+ def _merge_rules(self, des_rule_state, route_state):
+ """
+ Handle absent rules before adding desired rule entries to make sure
+ absent rule does not delete rule defined in desired state.
+ """
+ for entry in _get_config(des_rule_state):
+ rl = RouteRuleEntry(entry)
+ if rl.absent:
+ self._apply_absent_rules(rl)
+ for entry in _get_config(des_rule_state):
+ rl = RouteRuleEntry(entry)
+ if not rl.absent:
+ self._rules[rl.route_table].add(rl)
+
+ def _apply_absent_rules(self, rl):
+ """
+ Remove rules based on absent rules and treat missing property as
+ wildcard match.
+ """
+ absent_iface_table = rl.route_table
+ for route_table, rule_set in self._rules.items():
+ if absent_iface_table and absent_iface_table != route_table:
+ continue
+ new_rules = set()
+ for rule in rule_set:
+ if not rl.match(rule):
+ new_rules.add(rule)
+ if new_rules != rule_set:
+ self._rules[route_table] = new_rules
+
def verify(self, cur_rule_state):
current = RouteRuleState(
route_state=None,
@@ -159,6 +200,10 @@ class RouteRuleState:
Interface.IPV4: [],
Interface.IPV6: [],
}
+ if rules != self._cur_rules[route_table]:
+ route_rule_metadata[iface_name][
+ BaseIface.RULE_CHANGED_METADATA
+ ] = True
for rule in rules:
family = Interface.IPV6 if rule.is_ipv6 else Interface.IPV4
route_rule_metadata[iface_name][family].append(rule.to_dict())
diff --git a/libnmstate/schema.py b/libnmstate/schema.py
index b71576f..4018110 100644
--- a/libnmstate/schema.py
+++ b/libnmstate/schema.py
@@ -73,6 +73,8 @@ class RouteRule:
ROUTE_TABLE = "route-table"
USE_DEFAULT_PRIORITY = -1
USE_DEFAULT_ROUTE_TABLE = 0
+ STATE = "state"
+ STATE_ABSENT = "absent"
class DNS:
@@ -106,6 +108,7 @@ class InterfaceType:
ETHERNET = "ethernet"
LINUX_BRIDGE = "linux-bridge"
MAC_VLAN = "mac-vlan"
+ MAC_VTAP = "mac-vtap"
OVS_BRIDGE = "ovs-bridge"
OVS_INTERFACE = "ovs-interface"
OVS_PORT = "ovs-port"
@@ -372,7 +375,7 @@ class Team:
TYPE = InterfaceType.TEAM
CONFIG_SUBTREE = InterfaceType.TEAM
- PORT_SUBTREE = "ports"
+ PORT_SUBTREE = "port"
RUNNER_SUBTREE = "runner"
class Port:
@@ -429,3 +432,8 @@ class MacVlan:
PRIVATE = "private"
PASSTHRU = "passthru"
SOURCE = "source"
+
+
+class MacVtap(MacVlan):
+ TYPE = InterfaceType.MAC_VTAP
+ CONFIG_SUBTREE = "mac-vtap"
diff --git a/libnmstate/schemas/operational-state.yaml b/libnmstate/schemas/operational-state.yaml
index 1f9b378..3f81fa2 100644
--- a/libnmstate/schemas/operational-state.yaml
+++ b/libnmstate/schemas/operational-state.yaml
@@ -32,6 +32,7 @@ properties:
- "$ref": "#/definitions/interface-vrf/rw"
- "$ref": "#/definitions/interface-infiniband/rw"
- "$ref": "#/definitions/interface-mac-vlan/rw"
+ - "$ref": "#/definitions/interface-mac-vtap/rw"
- "$ref": "#/definitions/interface-other/rw"
routes:
type: object
@@ -665,6 +666,29 @@ definitions:
- unknown
promiscuous:
type: boolean
+ interface-mac-vtap:
+ rw:
+ properties:
+ type:
+ type: string
+ enum:
+ - mac-vtap
+ mac-vtap:
+ type: object
+ properties:
+ base-iface:
+ type: string
+ mode:
+ type: string
+ enum:
+ - private
+ - vepa
+ - bridge
+ - passthru
+ - source
+ - unknown
+ promiscuous:
+ type: boolean
interface-other:
rw:
properties:
@@ -723,6 +747,10 @@ definitions:
type: integer
route-table:
type: integer
+ state:
+ type: string
+ enum:
+ - absent
lldp:
ro:
properties:
diff --git a/nmstate.egg-info/PKG-INFO b/nmstate.egg-info/PKG-INFO
index 86af553..dabc333 100644
--- a/nmstate.egg-info/PKG-INFO
+++ b/nmstate.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: nmstate
-Version: 0.4.1
+Version: 1.0.0
Summary: Declarative network manager API
Home-page: https://nmstate.github.io/
Author: Edward Haas
diff --git a/nmstate.egg-info/SOURCES.txt b/nmstate.egg-info/SOURCES.txt
index da21f47..477181e 100644
--- a/nmstate.egg-info/SOURCES.txt
+++ b/nmstate.egg-info/SOURCES.txt
@@ -20,6 +20,8 @@ examples/linuxbrige_eth1_up.yml
examples/linuxbrige_eth1_up_port_vlan.yml
examples/mac_vlan_absent.yml
examples/mac_vlan_create.yml
+examples/mac_vtap_absent.yml
+examples/mac_vtap_create.yml
examples/ovsbridge_bond_create.yml
examples/ovsbridge_create.yml
examples/ovsbridge_delete.yml
@@ -61,6 +63,7 @@ libnmstate/ifaces/infiniband.py
libnmstate/ifaces/linux_bridge.py
libnmstate/ifaces/linux_bridge_port_vlan.py
libnmstate/ifaces/macvlan.py
+libnmstate/ifaces/macvtap.py
libnmstate/ifaces/ovs.py
libnmstate/ifaces/team.py
libnmstate/ifaces/vlan.py
@@ -74,9 +77,11 @@ libnmstate/nispor/bridge_port_vlan.py
libnmstate/nispor/dummy.py
libnmstate/nispor/ethernet.py
libnmstate/nispor/macvlan.py
+libnmstate/nispor/macvtap.py
libnmstate/nispor/ovs.py
libnmstate/nispor/plugin.py
libnmstate/nispor/route.py
+libnmstate/nispor/route_rule.py
libnmstate/nispor/vlan.py
libnmstate/nispor/vrf.py
libnmstate/nispor/vxlan.py
@@ -99,7 +104,7 @@ libnmstate/nm/macvlan.py
libnmstate/nm/ovs.py
libnmstate/nm/plugin.py
libnmstate/nm/profile.py
-libnmstate/nm/profile_state.py
+libnmstate/nm/profiles.py
libnmstate/nm/route.py
libnmstate/nm/sriov.py
libnmstate/nm/team.py
diff --git a/nmstate.egg-info/requires.txt b/nmstate.egg-info/requires.txt
index 8579746..ff542b6 100644
--- a/nmstate.egg-info/requires.txt
+++ b/nmstate.egg-info/requires.txt
@@ -3,4 +3,4 @@ PyGObject
PyYAML
setuptools
varlink
-nispor>=0.6.1
+nispor>=1.0.0
diff --git a/requirements.txt b/requirements.txt
index c8a7997..e9f472a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,4 +10,4 @@ PyGObject
PyYAML
setuptools
varlink
-nispor>=0.6.1
+nispor>=1.0.0