Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apply: bring "lo" back up if it's managed by NM (LP# 2034595) #408

Merged
merged 2 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion netplan_cli/cli/commands/apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,12 @@ def command_apply(self, run_generate=True, sync=False, exit_on_error=True, state
else:
logging.debug('no netplan generated networkd configuration exists')

loopback_connection = ''
if restart_nm:
logging.debug('netplan generated NM configuration changed, restarting NM')
if utils.nm_running():
if 'lo' in nm_ifaces:
loopback_connection = utils.nm_get_connection_for_interface('lo')
# restarting NM does not cause new config to be applied, need to shut down devices first
for device in devices:
if device not in nm_ifaces:
Expand Down Expand Up @@ -286,12 +289,20 @@ def command_apply(self, run_generate=True, sync=False, exit_on_error=True, state
if restart_nm:
# Flush all IP addresses of NM managed interfaces, to avoid NM creating
# new, non netplan-* connection profiles, using the existing IPs.
for iface in utils.nm_interfaces(restart_nm_glob, devices):
nm_interfaces = utils.nm_interfaces(restart_nm_glob, devices)
for iface in nm_interfaces:
utils.ip_addr_flush(iface)
# clear NM state, especially the [device].managed=true config, as that might have been
# re-set via an udev rule setting "NM_UNMANAGED=1"
shutil.rmtree('/run/NetworkManager/devices', ignore_errors=True)
utils.systemctl_network_manager('start', sync=sync)

# If 'lo' is in the nm_interfaces set we flushed it's IPs (see above) and disconnected it.
# NM will not bring it back automatically after restarting and we need to do that manually.
# For that, we need NM up and ready to accept commands
if 'lo' in nm_interfaces:
sync = True

if sync:
# 'nmcli' could be /usr/bin/nmcli or
# /snap/bin/nmcli -> /snap/bin/network-manager.nmcli
Expand All @@ -309,6 +320,13 @@ def command_apply(self, run_generate=True, sync=False, exit_on_error=True, state
break
time.sleep(0.5)

# If "lo" is managed by NM through Netplan, apply will flush its addresses and disconnect it.
# NM will not bring it back automatically.
# This is a possible scenario with netplan-everywhere. If a user tries to change the 'lo'
# connection with nmcli for example, NM will create a persistent nmconnection file and emit a YAML for it.
if 'lo' in nm_interfaces and loopback_connection:
utils.nm_bring_interface_up(loopback_connection)

@staticmethod
def is_composite_member(composites, phy):
"""
Expand Down
14 changes: 14 additions & 0 deletions netplan_cli/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ def nm_interfaces(paths, devices):
return interfaces


def nm_get_connection_for_interface(interface: str) -> str:
output = nmcli_out(['-m', 'tabular', '-f', 'GENERAL.CONNECTION', 'device', 'show', interface])
lines = output.strip().split('\n')
connection = lines[1]
return connection if connection != '--' else ''


def nm_bring_interface_up(connection: str) -> None: # pragma: nocover (must be covered by NM autopkgtests)
try:
nmcli(['connection', 'up', connection])
except subprocess.CalledProcessError:
pass


def systemctl_network_manager(action, sync=False):
# If the network-manager snap is installed use its service
# name rather than the one of the deb packaged NetworkManager
Expand Down
12 changes: 12 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,15 @@ def test_ip_addr_flush(self):
self.assertEqual(self.mock_cmd.calls(), [
['ip', 'addr', 'flush', 'eth42']
])

@patch('netplan_cli.cli.utils.nmcli_out')
def test_nm_get_connection_for_interface(self, nmcli):
nmcli.return_value = 'CONNECTION \nlo \n'
out = utils.nm_get_connection_for_interface('lo')
self.assertEqual(out, 'lo')

@patch('netplan_cli.cli.utils.nmcli_out')
def test_nm_get_connection_for_interface_no_connection(self, nmcli):
nmcli.return_value = 'CONNECTION \n-- \n'
out = utils.nm_get_connection_for_interface('asd0')
self.assertEqual(out, '')
Loading