black: Cover all libnmstate
Signed-off-by: Edward Haas <edwardh@redhat.com>
This commit is contained in:
parent
e5b86c0c0e
commit
df3fda59a4
@ -27,5 +27,12 @@ from .netinfo import show
|
||||
from .prettystate import PrettyState
|
||||
|
||||
|
||||
__all__ = ['show', 'apply', 'commit', 'rollback', 'error', 'schema',
|
||||
'PrettyState']
|
||||
__all__ = [
|
||||
'show',
|
||||
'apply',
|
||||
'commit',
|
||||
'rollback',
|
||||
'error',
|
||||
'schema',
|
||||
'PrettyState',
|
||||
]
|
||||
|
@ -30,8 +30,7 @@ def set_bridge_ports_metadata(master_state, slave_state):
|
||||
|
||||
ports = master_state.get('bridge', {}).get('port', [])
|
||||
port = next(
|
||||
six.moves.filter(lambda n: n['name'] == slave_state['name'], ports),
|
||||
{}
|
||||
six.moves.filter(lambda n: n['name'] == slave_state['name'], ports), {}
|
||||
)
|
||||
slave_state['_brport_options'] = port
|
||||
|
||||
|
@ -19,4 +19,5 @@ import ipaddress
|
||||
|
||||
if not hasattr(ipaddress.IPv4Network, 'subnet_of'):
|
||||
from libnmstate.ipaddress_extender import IPv4Network as _IPv4Network
|
||||
|
||||
ipaddress.IPv4Network = _IPv4Network
|
||||
|
@ -26,6 +26,7 @@ class NmstateError(Exception):
|
||||
"""
|
||||
The base exception of libnmstate.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -33,6 +34,7 @@ class NmstateDependencyError(NmstateError):
|
||||
"""
|
||||
Nmstate requires external tools installed and/or started for desired state.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -44,6 +46,7 @@ class NmstateValueError(NmstateError, ValueError):
|
||||
* Nmstate schema issue.
|
||||
* Invalid value of desired property, like bond missing slave.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -51,6 +54,7 @@ class NmstatePermissionError(NmstateError, PermissionError):
|
||||
"""
|
||||
Permission deny when applying the desired state.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -58,6 +62,7 @@ class NmstateConflictError(NmstateError, RuntimeError):
|
||||
"""
|
||||
Something else is already editing the network state via Nmstate.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -65,6 +70,7 @@ class NmstateLibnmError(NmstateError):
|
||||
"""
|
||||
Exception for unexpected libnm failure.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -73,6 +79,7 @@ class NmstateVerificationError(NmstateError):
|
||||
After applied desired state, current state does not match desired state for
|
||||
unknown reason.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -80,6 +87,7 @@ class NmstateNotImplementedError(NmstateError, NotImplementedError):
|
||||
"""
|
||||
Desired feature is not supported by Nmstate yet.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -88,4 +96,5 @@ class NmstateInternalError(NmstateError):
|
||||
Unexpected behaviour happened. It is a bug of libnmstate which should be
|
||||
fixed.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
@ -49,7 +49,9 @@ def minimal_ethtool(interface):
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sockfd = sock.fileno()
|
||||
|
||||
ecmd = array.array('B', struct.pack('I39s', ETHTOOL_GSET, b'\x00'*39))
|
||||
ecmd = array.array(
|
||||
'B', struct.pack('I39s', ETHTOOL_GSET, b'\x00' * 39)
|
||||
)
|
||||
|
||||
interface = interface.encode('utf-8')
|
||||
ifreq = struct.pack('16sP', interface, ecmd.buffer_info()[0])
|
||||
|
@ -22,7 +22,6 @@ import ipaddress
|
||||
|
||||
|
||||
class IPv4Network(ipaddress.IPv4Network):
|
||||
|
||||
def subnet_of(self, other):
|
||||
"""Return True if this network is a subnet of other."""
|
||||
return self._is_subnet_of(self, other)
|
||||
@ -33,9 +32,14 @@ class IPv4Network(ipaddress.IPv4Network):
|
||||
# Always false if one is v4 and the other is v6.
|
||||
if a._version != b._version:
|
||||
raise TypeError(
|
||||
'{} and {} are not of the same version'.format(a, b))
|
||||
return (b.network_address <= a.network_address and
|
||||
b.broadcast_address >= a.broadcast_address)
|
||||
'{} and {} are not of the same version'.format(a, b)
|
||||
)
|
||||
return (
|
||||
b.network_address <= a.network_address
|
||||
and b.broadcast_address >= a.broadcast_address
|
||||
)
|
||||
except AttributeError:
|
||||
raise TypeError('Unable to test subnet containment '
|
||||
'between {} and {}'.format(a, b))
|
||||
raise TypeError(
|
||||
'Unable to test subnet containment '
|
||||
'between {} and {}'.format(a, b)
|
||||
)
|
||||
|
@ -22,9 +22,11 @@ KERNEL_MAIN_ROUTE_TABLE_ID = 254
|
||||
|
||||
|
||||
def is_ipv6_link_local_addr(ip, prefix):
|
||||
return (ip[:len(_IPV6_LINK_LOCAL_NETWORK_PREFIXES[0])]
|
||||
in _IPV6_LINK_LOCAL_NETWORK_PREFIXES and
|
||||
prefix >= _IPV6_LINK_LOCAL_NETWORK_PREFIX_LENGTH)
|
||||
return (
|
||||
ip[: len(_IPV6_LINK_LOCAL_NETWORK_PREFIXES[0])]
|
||||
in _IPV6_LINK_LOCAL_NETWORK_PREFIXES
|
||||
and prefix >= _IPV6_LINK_LOCAL_NETWORK_PREFIX_LENGTH
|
||||
)
|
||||
|
||||
|
||||
def is_ipv6_address(addr):
|
||||
|
@ -52,21 +52,21 @@ def generate_ifaces_metadata(desired_state, current_state):
|
||||
current_state.interfaces,
|
||||
master_type='bond',
|
||||
get_slaves_func=_get_bond_slaves_from_state,
|
||||
set_metadata_func=_set_common_slaves_metadata
|
||||
set_metadata_func=_set_common_slaves_metadata,
|
||||
)
|
||||
_generate_link_master_metadata(
|
||||
desired_state.interfaces,
|
||||
current_state.interfaces,
|
||||
master_type='ovs-bridge',
|
||||
get_slaves_func=_get_ovs_slaves_from_state,
|
||||
set_metadata_func=_set_ovs_bridge_ports_metadata
|
||||
set_metadata_func=_set_ovs_bridge_ports_metadata,
|
||||
)
|
||||
_generate_link_master_metadata(
|
||||
desired_state.interfaces,
|
||||
current_state.interfaces,
|
||||
master_type='linux-bridge',
|
||||
get_slaves_func=linux_bridge.get_slaves_from_state,
|
||||
set_metadata_func=linux_bridge.set_bridge_ports_metadata
|
||||
set_metadata_func=linux_bridge.set_bridge_ports_metadata,
|
||||
)
|
||||
_generate_dns_metadata(desired_state, current_state)
|
||||
_generate_route_metadata(desired_state, current_state)
|
||||
@ -92,8 +92,7 @@ def _set_ovs_bridge_ports_metadata(master_state, slave_state):
|
||||
|
||||
ports = master_state.get('bridge', {}).get('port', [])
|
||||
port = next(
|
||||
six.moves.filter(lambda n: n['name'] == slave_state['name'], ports),
|
||||
{}
|
||||
six.moves.filter(lambda n: n['name'] == slave_state['name'], ports), {}
|
||||
)
|
||||
slave_state[BRPORT_OPTIONS] = port
|
||||
|
||||
@ -110,11 +109,13 @@ def _get_ovs_slaves_from_state(iface_state, default=()):
|
||||
return [p['name'] for p in ports]
|
||||
|
||||
|
||||
def _generate_link_master_metadata(ifaces_desired_state,
|
||||
ifaces_current_state,
|
||||
master_type,
|
||||
get_slaves_func,
|
||||
set_metadata_func):
|
||||
def _generate_link_master_metadata(
|
||||
ifaces_desired_state,
|
||||
ifaces_current_state,
|
||||
master_type,
|
||||
get_slaves_func,
|
||||
set_metadata_func,
|
||||
):
|
||||
"""
|
||||
Given master's slaves, add to the slave interface the master information.
|
||||
|
||||
@ -136,15 +137,17 @@ def _generate_link_master_metadata(ifaces_desired_state,
|
||||
if slave in ifaces_desired_state:
|
||||
set_metadata_func(master_state, ifaces_desired_state[slave])
|
||||
elif slave in ifaces_current_state:
|
||||
ifaces_desired_state[slave] = {'name': slave,
|
||||
'state': master_state['state']}
|
||||
ifaces_desired_state[slave] = {
|
||||
'name': slave,
|
||||
'state': master_state['state'],
|
||||
}
|
||||
set_metadata_func(master_state, ifaces_desired_state[slave])
|
||||
|
||||
desired_slaves = get_slaves_func(master_state)
|
||||
current_master_state = ifaces_current_state.get(master_name)
|
||||
if desired_slaves and current_master_state:
|
||||
current_slaves = get_slaves_func(current_master_state)
|
||||
slaves2remove = (set(current_slaves) - set(desired_slaves))
|
||||
slaves2remove = set(current_slaves) - set(desired_slaves)
|
||||
for slave in slaves2remove:
|
||||
if slave not in ifaces_desired_state:
|
||||
ifaces_desired_state[slave] = {'name': slave}
|
||||
@ -160,13 +163,18 @@ def _generate_link_master_metadata(ifaces_desired_state,
|
||||
if slave in ifaces_desired_state:
|
||||
iface_state = ifaces_desired_state.get(master_name, {})
|
||||
master_has_no_slaves_specified_in_desired = (
|
||||
get_slaves_func(iface_state, None) is None)
|
||||
get_slaves_func(iface_state, None) is None
|
||||
)
|
||||
slave_has_no_master_specified_in_desired = (
|
||||
ifaces_desired_state[slave].get(MASTER) is None)
|
||||
if (slave_has_no_master_specified_in_desired and
|
||||
master_has_no_slaves_specified_in_desired):
|
||||
ifaces_desired_state[slave].get(MASTER) is None
|
||||
)
|
||||
if (
|
||||
slave_has_no_master_specified_in_desired
|
||||
and master_has_no_slaves_specified_in_desired
|
||||
):
|
||||
set_metadata_func(
|
||||
master_state, ifaces_desired_state[slave])
|
||||
master_state, ifaces_desired_state[slave]
|
||||
)
|
||||
|
||||
|
||||
def _generate_route_metadata(desired_state, current_state):
|
||||
@ -184,20 +192,20 @@ def _generate_route_metadata(desired_state, current_state):
|
||||
elif current_iface_state:
|
||||
desired_iface_state = desired_state.interfaces[iface_name] = {
|
||||
Interface.NAME: iface_name,
|
||||
Interface.TYPE: current_iface_state[Interface.TYPE]
|
||||
Interface.TYPE: current_iface_state[Interface.TYPE],
|
||||
}
|
||||
_attach_route_metadata(desired_iface_state, routes)
|
||||
|
||||
|
||||
def _attach_route_metadata(iface_state, routes):
|
||||
_init_iface_route_metadata(iface_state, Interface.IPV4)
|
||||
_init_iface_route_metadata(iface_state, Interface.IPV6)
|
||||
_init_iface_route_metadata(iface_state, Interface.IPV4)
|
||||
_init_iface_route_metadata(iface_state, Interface.IPV6)
|
||||
|
||||
for route in routes:
|
||||
if iplib.is_ipv6_address(route.destination):
|
||||
iface_state[Interface.IPV6][ROUTES].append(route.to_dict())
|
||||
else:
|
||||
iface_state[Interface.IPV4][ROUTES].append(route.to_dict())
|
||||
for route in routes:
|
||||
if iplib.is_ipv6_address(route.destination):
|
||||
iface_state[Interface.IPV6][ROUTES].append(route.to_dict())
|
||||
else:
|
||||
iface_state[Interface.IPV4][ROUTES].append(route.to_dict())
|
||||
|
||||
|
||||
def _init_iface_route_metadata(iface_state, ip_key):
|
||||
@ -219,18 +227,28 @@ def _generate_dns_metadata(desired_state, current_state):
|
||||
if _dns_config_not_changed(desired_state, current_state):
|
||||
_preserve_current_dns_metadata(desired_state, current_state)
|
||||
else:
|
||||
ifaces_routes = {ifname: [r.to_dict() for r in routes]
|
||||
for ifname, routes in
|
||||
six.viewitems(desired_state.config_iface_routes)}
|
||||
ifaces_routes = {
|
||||
ifname: [r.to_dict() for r in routes]
|
||||
for ifname, routes in six.viewitems(
|
||||
desired_state.config_iface_routes
|
||||
)
|
||||
}
|
||||
ipv4_iface, ipv6_iface = nm.dns.find_interfaces_for_name_servers(
|
||||
ifaces_routes
|
||||
)
|
||||
_save_dns_metadata(desired_state, current_state, ipv4_iface,
|
||||
ipv6_iface, servers, searches)
|
||||
_save_dns_metadata(
|
||||
desired_state,
|
||||
current_state,
|
||||
ipv4_iface,
|
||||
ipv6_iface,
|
||||
servers,
|
||||
searches,
|
||||
)
|
||||
|
||||
|
||||
def _save_dns_metadata(desired_state, current_state, ipv4_iface, ipv6_iface,
|
||||
servers, searches):
|
||||
def _save_dns_metadata(
|
||||
desired_state, current_state, ipv4_iface, ipv6_iface, servers, searches
|
||||
):
|
||||
index = 0
|
||||
searches_saved = False
|
||||
for server in servers:
|
||||
@ -244,10 +262,12 @@ def _save_dns_metadata(desired_state, current_state, ipv4_iface, ipv6_iface,
|
||||
if not iface_name:
|
||||
raise NmstateValueError(
|
||||
'Failed to find suitable interface for saving DNS '
|
||||
'name servers: %s' % server)
|
||||
'name servers: %s' % server
|
||||
)
|
||||
|
||||
_include_name_only_iface_state(
|
||||
desired_state, current_state, [iface_name])
|
||||
desired_state, current_state, [iface_name]
|
||||
)
|
||||
iface_state = desired_state.interfaces[iface_name]
|
||||
if family not in iface_state:
|
||||
iface_state[family] = {}
|
||||
@ -256,7 +276,7 @@ def _save_dns_metadata(desired_state, current_state, ipv4_iface, ipv6_iface,
|
||||
iface_state[family][DNS_METADATA] = {
|
||||
DNS.SERVER: [server],
|
||||
DNS.SEARCH: [] if searches_saved else searches,
|
||||
DNS_METADATA_PRIORITY: nm.dns.DNS_PRIORITY_STATIC_BASE + index
|
||||
DNS_METADATA_PRIORITY: nm.dns.DNS_PRIORITY_STATIC_BASE + index,
|
||||
}
|
||||
else:
|
||||
iface_state[family][DNS_METADATA][DNS.SERVER].append(server)
|
||||
@ -265,14 +285,17 @@ def _save_dns_metadata(desired_state, current_state, ipv4_iface, ipv6_iface,
|
||||
|
||||
|
||||
def _include_name_only_iface_state(desired_state, current_state, iface_names):
|
||||
ifnames = (set(iface_names) & set(current_state.interfaces) -
|
||||
set(desired_state.interfaces))
|
||||
ifnames = set(iface_names) & set(current_state.interfaces) - set(
|
||||
desired_state.interfaces
|
||||
)
|
||||
for ifname in ifnames:
|
||||
desired_state.interfaces[ifname] = {Interface.NAME: ifname}
|
||||
|
||||
for iface_name in iface_names:
|
||||
if iface_name not in desired_state.interfaces and \
|
||||
iface_name in current_state.interfaces:
|
||||
if (
|
||||
iface_name not in desired_state.interfaces
|
||||
and iface_name in current_state.interfaces
|
||||
):
|
||||
desired_state.interfaces[iface_name] = {Interface.NAME: iface_name}
|
||||
|
||||
|
||||
@ -280,10 +303,11 @@ def _clear_current_dns_config(desired_state, current_state):
|
||||
client = nm.nmclient.client()
|
||||
current_dns_ifaces = nm.dns.get_dns_config_iface_names(
|
||||
nm.ipv4.acs_and_ip_profiles(client),
|
||||
nm.ipv6.acs_and_ip_profiles(client)
|
||||
nm.ipv6.acs_and_ip_profiles(client),
|
||||
)
|
||||
_include_name_only_iface_state(
|
||||
desired_state, current_state, current_dns_ifaces)
|
||||
desired_state, current_state, current_dns_ifaces
|
||||
)
|
||||
|
||||
|
||||
def _dns_config_not_changed(desired_state, current_state):
|
||||
@ -297,18 +321,21 @@ def _dns_config_not_changed(desired_state, current_state):
|
||||
client = nm.nmclient.client()
|
||||
iface_dns_configs = nm.dns.get_indexed_dns_config_by_iface(
|
||||
nm.ipv4.acs_and_ip_profiles(client),
|
||||
nm.ipv6.acs_and_ip_profiles(client)
|
||||
nm.ipv6.acs_and_ip_profiles(client),
|
||||
)
|
||||
for iface_name, iface_dns_config in six.viewitems(iface_dns_configs):
|
||||
if iface_name not in desired_state.interfaces:
|
||||
continue
|
||||
iface_state = desired_state.interfaces[iface_name]
|
||||
if Interface.STATE in iface_state and \
|
||||
iface_state[Interface.STATE] != InterfaceState.UP:
|
||||
if (
|
||||
Interface.STATE in iface_state
|
||||
and iface_state[Interface.STATE] != InterfaceState.UP
|
||||
):
|
||||
return False
|
||||
for family in six.viewkeys(iface_dns_config):
|
||||
if family in iface_state and \
|
||||
not iface_state[family].get('enabled'):
|
||||
if family in iface_state and not iface_state[family].get(
|
||||
'enabled'
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -317,7 +344,7 @@ def _preserve_current_dns_metadata(desired_state, current_state):
|
||||
client = nm.nmclient.client()
|
||||
iface_dns_configs = nm.dns.get_indexed_dns_config_by_iface(
|
||||
nm.ipv4.acs_and_ip_profiles(client),
|
||||
nm.ipv6.acs_and_ip_profiles(client)
|
||||
nm.ipv6.acs_and_ip_profiles(client),
|
||||
)
|
||||
for iface_name, iface_dns_config in six.viewitems(iface_dns_configs):
|
||||
if iface_name not in desired_state.interfaces:
|
||||
|
@ -56,7 +56,8 @@ def apply(desired_state, verify_change=True, commit=True, rollback_timeout=60):
|
||||
validator.validate_dns(desired_state)
|
||||
|
||||
checkpoint = _apply_ifaces_state(
|
||||
state.State(desired_state), verify_change, commit, rollback_timeout)
|
||||
state.State(desired_state), verify_change, commit, rollback_timeout
|
||||
)
|
||||
if checkpoint:
|
||||
return str(checkpoint.dbuspath)
|
||||
|
||||
@ -105,8 +106,9 @@ def _choose_checkpoint(dbuspath):
|
||||
return checkpoint
|
||||
|
||||
|
||||
def _apply_ifaces_state(desired_state, verify_change, commit,
|
||||
rollback_timeout):
|
||||
def _apply_ifaces_state(
|
||||
desired_state, verify_change, commit, rollback_timeout
|
||||
):
|
||||
current_state = state.State(netinfo.show())
|
||||
|
||||
desired_state.sanitize_ethernet(current_state)
|
||||
@ -121,15 +123,16 @@ def _apply_ifaces_state(desired_state, verify_change, commit,
|
||||
new_interfaces = _list_new_interfaces(desired_state, current_state)
|
||||
|
||||
try:
|
||||
with nm.checkpoint.CheckPoint(autodestroy=commit,
|
||||
timeout=rollback_timeout) as checkpoint:
|
||||
with nm.checkpoint.CheckPoint(
|
||||
autodestroy=commit, timeout=rollback_timeout
|
||||
) as checkpoint:
|
||||
with _setup_providers():
|
||||
_add_interfaces(new_interfaces, desired_state)
|
||||
with _setup_providers():
|
||||
current_state = state.State(netinfo.show())
|
||||
state2edit = _create_editable_desired_state(desired_state,
|
||||
current_state,
|
||||
new_interfaces)
|
||||
state2edit = _create_editable_desired_state(
|
||||
desired_state, current_state, new_interfaces
|
||||
)
|
||||
_edit_interfaces(state2edit)
|
||||
if verify_change:
|
||||
_verify_change(desired_state)
|
||||
@ -141,9 +144,9 @@ def _apply_ifaces_state(desired_state, verify_change, commit,
|
||||
raise NmstateConflictError('Error creating a check point')
|
||||
|
||||
|
||||
def _create_editable_desired_state(desired_state,
|
||||
current_state,
|
||||
new_intefaces):
|
||||
def _create_editable_desired_state(
|
||||
desired_state, current_state, new_intefaces
|
||||
):
|
||||
"""
|
||||
Create a new state object that includes only existing interfaces which need
|
||||
to be edited/changed.
|
||||
@ -151,7 +154,8 @@ def _create_editable_desired_state(desired_state,
|
||||
state2edit = state.create_state(
|
||||
desired_state.state,
|
||||
interfaces_to_filter=(
|
||||
set(current_state.interfaces) - set(new_intefaces))
|
||||
set(current_state.interfaces) - set(new_intefaces)
|
||||
),
|
||||
)
|
||||
state2edit.merge_interfaces(current_state)
|
||||
return state2edit
|
||||
@ -159,11 +163,11 @@ def _create_editable_desired_state(desired_state,
|
||||
|
||||
def _list_new_interfaces(desired_state, current_state):
|
||||
return [
|
||||
name for name in
|
||||
six.viewkeys(desired_state.interfaces) -
|
||||
six.viewkeys(current_state.interfaces)
|
||||
if desired_state.interfaces[name].get(schema.Interface.STATE) not in (
|
||||
schema.InterfaceState.ABSENT, schema.InterfaceState.DOWN)
|
||||
name
|
||||
for name in six.viewkeys(desired_state.interfaces)
|
||||
- six.viewkeys(current_state.interfaces)
|
||||
if desired_state.interfaces[name].get(schema.Interface.STATE)
|
||||
not in (schema.InterfaceState.ABSENT, schema.InterfaceState.DOWN)
|
||||
]
|
||||
|
||||
|
||||
@ -182,7 +186,9 @@ def _setup_providers():
|
||||
if not success:
|
||||
raise NmstateLibnmError(
|
||||
'Unexpected failure of libnm when running the mainloop: {}'.format(
|
||||
mainloop.error))
|
||||
mainloop.error
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _add_interfaces(new_interfaces, desired_state):
|
||||
@ -203,16 +209,20 @@ def _edit_interfaces(state2edit):
|
||||
ifaces2edit = list(six.viewvalues(state2edit.interfaces))
|
||||
|
||||
iface2prepare = list(
|
||||
filter(lambda state: state.get('state') not in ('absent', 'down'),
|
||||
ifaces2edit)
|
||||
filter(
|
||||
lambda state: state.get('state') not in ('absent', 'down'),
|
||||
ifaces2edit,
|
||||
)
|
||||
)
|
||||
proxy_ifaces = nm.applier.prepare_proxy_ifaces_desired_state(iface2prepare)
|
||||
ifaces_configs = nm.applier.prepare_edited_ifaces_configuration(
|
||||
iface2prepare + proxy_ifaces)
|
||||
iface2prepare + proxy_ifaces
|
||||
)
|
||||
nm.applier.edit_existing_ifaces(ifaces_configs)
|
||||
|
||||
nm.applier.set_ifaces_admin_state(ifaces2edit + proxy_ifaces,
|
||||
con_profiles=ifaces_configs)
|
||||
nm.applier.set_ifaces_admin_state(
|
||||
ifaces2edit + proxy_ifaces, con_profiles=ifaces_configs
|
||||
)
|
||||
|
||||
|
||||
def _index_by_name(ifaces_state):
|
||||
|
@ -41,17 +41,21 @@ def show(include_status_data=False):
|
||||
report['capabilities'] = capabilities()
|
||||
|
||||
report[Constants.ROUTES] = {
|
||||
Route.RUNNING: (nm.ipv4.get_route_running() +
|
||||
nm.ipv6.get_route_running()),
|
||||
Route.CONFIG: (nm.ipv4.get_route_config() +
|
||||
nm.ipv6.get_route_config()),
|
||||
Route.RUNNING: (
|
||||
nm.ipv4.get_route_running() + nm.ipv6.get_route_running()
|
||||
),
|
||||
Route.CONFIG: (
|
||||
nm.ipv4.get_route_config() + nm.ipv6.get_route_config()
|
||||
),
|
||||
}
|
||||
|
||||
client = nm.nmclient.client()
|
||||
report[Constants.DNS] = {
|
||||
DNS.RUNNING: nm_dns.get_running(),
|
||||
DNS.CONFIG: nm_dns.get_config(nm.ipv4.acs_and_ip_profiles(client),
|
||||
nm.ipv6.acs_and_ip_profiles(client)),
|
||||
DNS.CONFIG: nm_dns.get_config(
|
||||
nm.ipv4.acs_and_ip_profiles(client),
|
||||
nm.ipv6.acs_and_ip_profiles(client),
|
||||
),
|
||||
}
|
||||
|
||||
validator.validate(report)
|
||||
@ -72,8 +76,10 @@ def interfaces():
|
||||
|
||||
nm.nmclient.client(refresh=True)
|
||||
|
||||
devices_info = [(dev, nm.device.get_device_common_info(dev))
|
||||
for dev in nm.device.list_devices()]
|
||||
devices_info = [
|
||||
(dev, nm.device.get_device_common_info(dev))
|
||||
for dev in nm.device.list_devices()
|
||||
]
|
||||
|
||||
for dev, devinfo in devices_info:
|
||||
type_id = devinfo['type_id']
|
||||
|
@ -31,11 +31,15 @@ def format_desired_current_state_diff(desired_state, current_state):
|
||||
pretty_desired_state = PrettyState(desired_state).yaml
|
||||
pretty_current_state = PrettyState(current_state).yaml
|
||||
|
||||
diff = ''.join(difflib.unified_diff(
|
||||
pretty_desired_state.splitlines(True),
|
||||
pretty_current_state.splitlines(True),
|
||||
fromfile='desired',
|
||||
tofile='current', n=3))
|
||||
diff = ''.join(
|
||||
difflib.unified_diff(
|
||||
pretty_desired_state.splitlines(True),
|
||||
pretty_current_state.splitlines(True),
|
||||
fromfile='desired',
|
||||
tofile='current',
|
||||
n=3,
|
||||
)
|
||||
)
|
||||
return (
|
||||
'\n'
|
||||
'desired\n'
|
||||
@ -46,10 +50,7 @@ def format_desired_current_state_diff(desired_state, current_state):
|
||||
'{}\n'
|
||||
'difference\n'
|
||||
'==========\n'
|
||||
'{}\n'.format(
|
||||
pretty_desired_state,
|
||||
pretty_current_state,
|
||||
diff)
|
||||
'{}\n'.format(pretty_desired_state, pretty_current_state, diff)
|
||||
)
|
||||
|
||||
|
||||
@ -63,8 +64,9 @@ class PrettyState(object):
|
||||
|
||||
@property
|
||||
def yaml(self):
|
||||
return yaml.dump(self.state, default_flow_style=False,
|
||||
explicit_start=True)
|
||||
return yaml.dump(
|
||||
self.state, default_flow_style=False, explicit_start=True
|
||||
)
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
@ -95,9 +97,8 @@ def order_state(state):
|
||||
|
||||
if iface_states is not None:
|
||||
state[Constants.INTERFACES] = [
|
||||
order_iface_state(iface_state) for iface_state in sorted(
|
||||
iface_states, key=itemgetter('name')
|
||||
)
|
||||
order_iface_state(iface_state)
|
||||
for iface_state in sorted(iface_states, key=itemgetter('name'))
|
||||
]
|
||||
|
||||
return state
|
||||
@ -112,8 +113,9 @@ def represent_unicode(_, data):
|
||||
|
||||
"""
|
||||
|
||||
return yaml.ScalarNode(tag=u'tag:yaml.org,2002:str',
|
||||
value=data.encode('utf-8'))
|
||||
return yaml.ScalarNode(
|
||||
tag=u'tag:yaml.org,2002:str', value=data.encode('utf-8')
|
||||
)
|
||||
|
||||
|
||||
def order_iface_state(iface_state):
|
||||
|
@ -23,7 +23,7 @@ import yaml
|
||||
def load(schema_name):
|
||||
return yaml.load(
|
||||
pkgutil.get_data('libnmstate', 'schemas/' + schema_name + '.yaml'),
|
||||
Loader=yaml.SafeLoader
|
||||
Loader=yaml.SafeLoader,
|
||||
)
|
||||
|
||||
|
||||
@ -99,7 +99,7 @@ class InterfaceType(object):
|
||||
OVS_BRIDGE,
|
||||
OVS_PORT,
|
||||
OVS_INTERFACE,
|
||||
VLAN
|
||||
VLAN,
|
||||
)
|
||||
|
||||
|
||||
|
@ -68,25 +68,34 @@ class RouteEntry(object):
|
||||
return hash(self.__keys())
|
||||
|
||||
def __keys(self):
|
||||
return (self.table_id, self.metric, self.destination,
|
||||
self.next_hop_address, self.next_hop_interface)
|
||||
return (
|
||||
self.table_id,
|
||||
self.metric,
|
||||
self.destination,
|
||||
self.next_hop_address,
|
||||
self.next_hop_interface,
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
key.replace('_', '-'): value
|
||||
for key, value in six.viewitems(vars(self)) if value is not None
|
||||
for key, value in six.viewitems(vars(self))
|
||||
if value is not None
|
||||
}
|
||||
|
||||
def __eq__(self, other):
|
||||
return self is other or self.__keys() == other.__keys()
|
||||
|
||||
def __lt__(self, other):
|
||||
return ((self.table_id or Route.USE_DEFAULT_ROUTE_TABLE,
|
||||
self.next_hop_interface or '',
|
||||
self.destination or '') <
|
||||
(other.table_id or Route.USE_DEFAULT_ROUTE_TABLE,
|
||||
other.next_hop_interface or '',
|
||||
other.destination or ''))
|
||||
return (
|
||||
self.table_id or Route.USE_DEFAULT_ROUTE_TABLE,
|
||||
self.next_hop_interface or '',
|
||||
self.destination or '',
|
||||
) < (
|
||||
other.table_id or Route.USE_DEFAULT_ROUTE_TABLE,
|
||||
other.next_hop_interface or '',
|
||||
other.destination or '',
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.to_dict())
|
||||
@ -150,7 +159,7 @@ class State(object):
|
||||
def state(self):
|
||||
self._state[Interface.KEY] = sorted(
|
||||
list(six.viewvalues(self._ifaces_state)),
|
||||
key=itemgetter(Interface.NAME)
|
||||
key=itemgetter(Interface.NAME),
|
||||
)
|
||||
return self._state
|
||||
|
||||
@ -171,7 +180,7 @@ class State(object):
|
||||
dns_conf = self._state.get(DNS.KEY, {}).get(DNS.CONFIG, {})
|
||||
return {
|
||||
DNS.SERVER: dns_conf.get(DNS.SERVER, []),
|
||||
DNS.SEARCH: dns_conf.get(DNS.SEARCH, [])
|
||||
DNS.SEARCH: dns_conf.get(DNS.SEARCH, []),
|
||||
}
|
||||
|
||||
def _complement_interface_empty_ip_subtrees(self):
|
||||
@ -212,12 +221,15 @@ class State(object):
|
||||
for family in ('ipv4', 'ipv6'):
|
||||
ip = iface_state[family]
|
||||
if ip.get('enabled') and (
|
||||
ip.get('dhcp') or ip.get('autoconf')):
|
||||
ip.get('dhcp') or ip.get('autoconf')
|
||||
):
|
||||
ip['address'] = []
|
||||
else:
|
||||
for dhcp_option in ('auto-routes',
|
||||
'auto-gateway',
|
||||
'auto-dns'):
|
||||
for dhcp_option in (
|
||||
'auto-routes',
|
||||
'auto-gateway',
|
||||
'auto-dns',
|
||||
):
|
||||
ip.pop(dhcp_option, None)
|
||||
|
||||
def verify_interfaces(self, other_state):
|
||||
@ -244,7 +256,7 @@ class State(object):
|
||||
raise NmstateVerificationError(
|
||||
format_desired_current_state_diff(
|
||||
{Route.KEY: [r.to_dict() for r in routes]},
|
||||
{Route.KEY: [r.to_dict() for r in other_routes]}
|
||||
{Route.KEY: [r.to_dict() for r in other_routes]},
|
||||
)
|
||||
)
|
||||
|
||||
@ -252,12 +264,10 @@ class State(object):
|
||||
if self.config_dns != other_state.config_dns:
|
||||
raise NmstateVerificationError(
|
||||
format_desired_current_state_diff(
|
||||
{
|
||||
DNS.KEY: self.config_dns
|
||||
},
|
||||
{
|
||||
DNS.KEY: other_state.config_dns
|
||||
}))
|
||||
{DNS.KEY: self.config_dns},
|
||||
{DNS.KEY: other_state.config_dns},
|
||||
)
|
||||
)
|
||||
|
||||
def normalize_for_verification(self):
|
||||
self._clean_sanitize_ethernet()
|
||||
@ -277,8 +287,9 @@ class State(object):
|
||||
This is a reverse recursive update operation.
|
||||
"""
|
||||
other_state = State(other_state.state)
|
||||
for name in (six.viewkeys(self.interfaces) &
|
||||
six.viewkeys(other_state.interfaces)):
|
||||
for name in six.viewkeys(self.interfaces) & six.viewkeys(
|
||||
other_state.interfaces
|
||||
):
|
||||
dict_update(other_state.interfaces[name], self.interfaces[name])
|
||||
self._ifaces_state[name] = other_state.interfaces[name]
|
||||
|
||||
@ -297,14 +308,17 @@ class State(object):
|
||||
"""
|
||||
other_routes = set()
|
||||
for ifname, routes in six.viewitems(other_state.config_iface_routes):
|
||||
if (ifname in self.config_iface_routes or
|
||||
self._is_interface_routable(ifname, routes)):
|
||||
if (
|
||||
ifname in self.config_iface_routes
|
||||
or self._is_interface_routable(ifname, routes)
|
||||
):
|
||||
other_routes |= set(routes)
|
||||
|
||||
self_routes = {
|
||||
route
|
||||
for routes in six.viewvalues(self.config_iface_routes)
|
||||
for route in routes if not route.absent
|
||||
for route in routes
|
||||
if not route.absent
|
||||
}
|
||||
|
||||
absent_routes = set()
|
||||
@ -334,14 +348,16 @@ class State(object):
|
||||
if iface_up:
|
||||
ipv4_state = ifstate.get(Interface.IPV4, {})
|
||||
ipv4_disabled = ipv4_state.get('enabled') is False
|
||||
if (ipv4_disabled and
|
||||
any(not is_ipv6_address(r.destination) for r in routes)):
|
||||
if ipv4_disabled and any(
|
||||
not is_ipv6_address(r.destination) for r in routes
|
||||
):
|
||||
return False
|
||||
|
||||
ipv6_state = ifstate.get(Interface.IPV6, {})
|
||||
ipv6_disabled = ipv6_state.get('enabled') is False
|
||||
if (ipv6_disabled and
|
||||
any(is_ipv6_address(r.destination) for r in routes)):
|
||||
if ipv6_disabled and any(
|
||||
is_ipv6_address(r.destination) for r in routes
|
||||
):
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -370,16 +386,18 @@ class State(object):
|
||||
ifaces = {}
|
||||
for ifname, ifstate in six.viewitems(self.interfaces):
|
||||
is_virt_down = (
|
||||
ifstate.get(Interface.STATE) == InterfaceState.DOWN and
|
||||
ifstate.get(Interface.TYPE) in InterfaceType.VIRT_TYPES
|
||||
ifstate.get(Interface.STATE) == InterfaceState.DOWN
|
||||
and ifstate.get(Interface.TYPE) in InterfaceType.VIRT_TYPES
|
||||
)
|
||||
if not is_virt_down:
|
||||
ifaces[ifname] = ifstate
|
||||
self._ifaces_state = ifaces
|
||||
|
||||
def _index_interfaces_state_by_name(self):
|
||||
return {iface[Interface.NAME]: iface
|
||||
for iface in self._state.get(Interface.KEY, [])}
|
||||
return {
|
||||
iface[Interface.NAME]: iface
|
||||
for iface in self._state.get(Interface.KEY, [])
|
||||
}
|
||||
|
||||
def _index_routes_by_iface(self):
|
||||
iface_routes = defaultdict(list)
|
||||
@ -394,9 +412,11 @@ class State(object):
|
||||
for ifstate in six.viewvalues(self.interfaces):
|
||||
ethernet_state = ifstate.get(Ethernet.CONFIG_SUBTREE)
|
||||
if ethernet_state:
|
||||
for key in (Ethernet.AUTO_NEGOTIATION,
|
||||
Ethernet.SPEED,
|
||||
Ethernet.DUPLEX):
|
||||
for key in (
|
||||
Ethernet.AUTO_NEGOTIATION,
|
||||
Ethernet.SPEED,
|
||||
Ethernet.DUPLEX,
|
||||
):
|
||||
if ethernet_state.get(key, None) is None:
|
||||
ethernet_state.pop(key, None)
|
||||
if not ethernet_state:
|
||||
@ -409,7 +429,8 @@ class State(object):
|
||||
def _sort_bridge_ports(self):
|
||||
for ifstate in six.viewvalues(self.interfaces):
|
||||
ifstate.get('bridge', {}).get('port', []).sort(
|
||||
key=itemgetter('name'))
|
||||
key=itemgetter('name')
|
||||
)
|
||||
|
||||
def _canonicalize_ipv6(self):
|
||||
for ifstate in six.viewvalues(self.interfaces):
|
||||
@ -420,9 +441,11 @@ class State(object):
|
||||
def _remove_iface_ipv6_link_local_addr(self):
|
||||
for ifstate in six.viewvalues(self.interfaces):
|
||||
ifstate['ipv6']['address'] = list(
|
||||
addr for addr in ifstate['ipv6']['address']
|
||||
if not iplib.is_ipv6_link_local_addr(addr['ip'],
|
||||
addr['prefix-length'])
|
||||
addr
|
||||
for addr in ifstate['ipv6']['address']
|
||||
if not iplib.is_ipv6_link_local_addr(
|
||||
addr['ip'], addr['prefix-length']
|
||||
)
|
||||
)
|
||||
|
||||
def _sort_ip_addresses(self):
|
||||
@ -445,16 +468,17 @@ class State(object):
|
||||
raise NmstateVerificationError(
|
||||
format_desired_current_state_diff(
|
||||
self.interfaces[ifname],
|
||||
current_state.interfaces[ifname]
|
||||
current_state.interfaces[ifname],
|
||||
)
|
||||
)
|
||||
|
||||
def _assert_interfaces_included_in(self, current_state):
|
||||
if not (set(self.interfaces) <= set(
|
||||
current_state.interfaces)):
|
||||
if not (set(self.interfaces) <= set(current_state.interfaces)):
|
||||
raise NmstateVerificationError(
|
||||
format_desired_current_state_diff(self.interfaces,
|
||||
current_state.interfaces))
|
||||
format_desired_current_state_diff(
|
||||
self.interfaces, current_state.interfaces
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def _config_routes(self):
|
||||
@ -479,8 +503,12 @@ def dict_update(origin_data, to_merge_data):
|
||||
return origin_data
|
||||
|
||||
|
||||
def _validate_routes(iface_route_sets, iface_enable_states,
|
||||
ipv4_enable_states, ipv6_enable_states):
|
||||
def _validate_routes(
|
||||
iface_route_sets,
|
||||
iface_enable_states,
|
||||
ipv4_enable_states,
|
||||
ipv6_enable_states,
|
||||
):
|
||||
"""
|
||||
Check whether user desire routes next hop to:
|
||||
* down/absent interface
|
||||
@ -495,7 +523,8 @@ def _validate_routes(iface_route_sets, iface_enable_states,
|
||||
raise NmstateValueError('Cannot set route to non-exist interface')
|
||||
if iface_enable_state != InterfaceState.UP:
|
||||
raise NmstateValueError(
|
||||
'Cannot set route to {} interface'.format(iface_enable_state))
|
||||
'Cannot set route to {} interface'.format(iface_enable_state)
|
||||
)
|
||||
# Interface is already check, so the ip enable status should be defined
|
||||
ipv4_enabled = ipv4_enable_states[iface_name]
|
||||
ipv6_enabled = ipv6_enable_states[iface_name]
|
||||
@ -503,10 +532,12 @@ def _validate_routes(iface_route_sets, iface_enable_states,
|
||||
if iplib.is_ipv6_address(route_obj.destination):
|
||||
if not ipv6_enabled:
|
||||
raise NmstateValueError(
|
||||
'Cannot set IPv6 route when IPv6 is disabled')
|
||||
'Cannot set IPv6 route when IPv6 is disabled'
|
||||
)
|
||||
elif not ipv4_enabled:
|
||||
raise NmstateValueError(
|
||||
'Cannot set IPv4 route when IPv4 is disabled')
|
||||
raise NmstateValueError(
|
||||
'Cannot set IPv4 route when IPv4 is disabled'
|
||||
)
|
||||
|
||||
|
||||
def _get_iface_enable_states(desire_state, current_state):
|
||||
@ -524,8 +555,9 @@ def _get_iface_enable_states(desire_state, current_state):
|
||||
def _get_ip_enable_states(family, desire_state, current_state):
|
||||
ip_enable_states = {}
|
||||
for iface_name, iface_state in six.viewitems(current_state.interfaces):
|
||||
ip_enable_states[iface_name] = iface_state.get(
|
||||
family, {}).get('enabled', False)
|
||||
ip_enable_states[iface_name] = iface_state.get(family, {}).get(
|
||||
'enabled', False
|
||||
)
|
||||
for iface_name, iface_state in six.viewitems(desire_state.interfaces):
|
||||
ip_enable_state = iface_state.get(family, {}).get('enabled')
|
||||
if ip_enable_state is not None:
|
||||
@ -536,8 +568,9 @@ def _get_ip_enable_states(family, desire_state, current_state):
|
||||
return ip_enable_states
|
||||
|
||||
|
||||
def _route_is_valid(route_obj, iface_enable_states, ipv4_enable_states,
|
||||
ipv6_enable_states):
|
||||
def _route_is_valid(
|
||||
route_obj, iface_enable_states, ipv4_enable_states, ipv6_enable_states
|
||||
):
|
||||
"""
|
||||
Return False when route is next hop to any of these interfaces:
|
||||
* Interface not in InterfaceState.UP state.
|
||||
|
@ -57,12 +57,13 @@ def validate_interface_capabilities(ifaces_state, capabilities):
|
||||
is_ovs_type = iface_type in (
|
||||
nm.ovs.BRIDGE_TYPE,
|
||||
nm.ovs.PORT_TYPE,
|
||||
nm.ovs.INTERNAL_INTERFACE_TYPE
|
||||
nm.ovs.INTERNAL_INTERFACE_TYPE,
|
||||
)
|
||||
if is_ovs_type and not has_ovs_capability:
|
||||
raise NmstateDependencyError(
|
||||
"Open vSwitch NetworkManager support not installed "
|
||||
"and started")
|
||||
"and started"
|
||||
)
|
||||
|
||||
|
||||
def validate_interfaces_state(desired_state, current_state):
|
||||
@ -71,7 +72,8 @@ def validate_interfaces_state(desired_state, current_state):
|
||||
|
||||
def validate_link_aggregation_state(desired_state, current_state):
|
||||
available_ifaces = {
|
||||
ifname for ifname, ifstate in six.viewitems(desired_state.interfaces)
|
||||
ifname
|
||||
for ifname, ifstate in six.viewitems(desired_state.interfaces)
|
||||
if ifstate.get('state') != 'absent'
|
||||
}
|
||||
available_ifaces |= set(current_state.interfaces)
|
||||
@ -85,11 +87,15 @@ def validate_link_aggregation_state(desired_state, current_state):
|
||||
if not (slaves <= available_ifaces):
|
||||
raise NmstateValueError(
|
||||
"Link aggregation has missing slave: {}".format(
|
||||
iface_state))
|
||||
iface_state
|
||||
)
|
||||
)
|
||||
if slaves & specified_slaves:
|
||||
raise NmstateValueError(
|
||||
"Link aggregation has reused slave: {}".format(
|
||||
iface_state))
|
||||
iface_state
|
||||
)
|
||||
)
|
||||
specified_slaves |= slaves
|
||||
|
||||
|
||||
@ -97,10 +103,15 @@ def validate_dhcp(state):
|
||||
for iface_state in state[Constants.INTERFACES]:
|
||||
for family in ('ipv4', 'ipv6'):
|
||||
ip = iface_state.get(family, {})
|
||||
if ip.get('enabled') and ip.get('address') and \
|
||||
(ip.get('dhcp') or ip.get('autoconf')):
|
||||
logging.warning('%s addresses are ignored when '
|
||||
'dynamic IP is enabled', family)
|
||||
if (
|
||||
ip.get('enabled')
|
||||
and ip.get('address')
|
||||
and (ip.get('dhcp') or ip.get('autoconf'))
|
||||
):
|
||||
logging.warning(
|
||||
'%s addresses are ignored when ' 'dynamic IP is enabled',
|
||||
family,
|
||||
)
|
||||
|
||||
|
||||
def validate_dns(state):
|
||||
@ -108,11 +119,13 @@ def validate_dns(state):
|
||||
Only support at most 2 name servers now:
|
||||
https://nmstate.atlassian.net/browse/NMSTATE-220
|
||||
"""
|
||||
dns_servers = state.get(
|
||||
DNS.KEY, {}).get(DNS.CONFIG, {}).get(DNS.SERVER, [])
|
||||
dns_servers = (
|
||||
state.get(DNS.KEY, {}).get(DNS.CONFIG, {}).get(DNS.SERVER, [])
|
||||
)
|
||||
if len(dns_servers) > 2:
|
||||
raise NmstateNotImplementedError(
|
||||
'Nmstate only support at most 2 DNS name servers')
|
||||
'Nmstate only support at most 2 DNS name servers'
|
||||
)
|
||||
|
||||
|
||||
def validate_routes(desired_state, current_state):
|
||||
@ -132,11 +145,13 @@ def validate_routes(desired_state, current_state):
|
||||
if desired_iface_state or current_iface_state:
|
||||
_assert_iface_is_up(desired_iface_state, current_iface_state)
|
||||
if any(is_ipv6_address(route.destination) for route in routes):
|
||||
_assert_iface_ipv6_enabled(desired_iface_state,
|
||||
current_iface_state)
|
||||
_assert_iface_ipv6_enabled(
|
||||
desired_iface_state, current_iface_state
|
||||
)
|
||||
if any(not is_ipv6_address(route.destination) for route in routes):
|
||||
_assert_iface_ipv4_enabled(desired_iface_state,
|
||||
current_iface_state)
|
||||
_assert_iface_ipv4_enabled(
|
||||
desired_iface_state, current_iface_state
|
||||
)
|
||||
else:
|
||||
raise NmstateRouteWithNoInterfaceError(str(routes))
|
||||
|
||||
@ -160,12 +175,14 @@ def _assert_iface_is_up(desired_iface_state, current_iface_state):
|
||||
|
||||
def _assert_iface_ipv4_enabled(desired_iface_state, current_iface_state):
|
||||
_assert_iface_ip_enabled(
|
||||
desired_iface_state, current_iface_state, schema.Interface.IPV4)
|
||||
desired_iface_state, current_iface_state, schema.Interface.IPV4
|
||||
)
|
||||
|
||||
|
||||
def _assert_iface_ipv6_enabled(desired_iface_state, current_iface_state):
|
||||
_assert_iface_ip_enabled(
|
||||
desired_iface_state, current_iface_state, schema.Interface.IPV6)
|
||||
desired_iface_state, current_iface_state, schema.Interface.IPV6
|
||||
)
|
||||
|
||||
|
||||
def _assert_iface_ip_enabled(desired_iface_state, current_iface_state, ipkey):
|
||||
|
Loading…
x
Reference in New Issue
Block a user