Skip to content

Commit 9c147e8

Browse files
Allow disabling of network activation (SC-307) (canonical#1048)
In canonical#919 (81299de), we refactored some of the code used to bring up networks across distros. Previously, the call to bring up network interfaces during 'init' stage unintentionally resulted in a no-op such that network interfaces were NEVER brought up by cloud-init, even if new network interfaces were found after crawling the metadata. The code was altered to bring up these discovered network interfaces. On ubuntu, this results in a 'netplan apply' call during 'init' stage for any ubuntu-based distro on a datasource that has a NETWORK dependency. On GCE, this additional 'netplan apply' conflicts with the google-guest-agent service, resulting in an instance that can no be connected to. This commit adds a 'disable_network_activation' option that can be enabled in /etc/cloud.cfg to disable the activation of network interfaces in 'init' stage. LP: #1938299
1 parent 3d2bac8 commit 9c147e8

File tree

5 files changed

+89
-2
lines changed

5 files changed

+89
-2
lines changed

cloudinit/cmd/main.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,12 @@ def purge_cache_on_python_version_change(init):
239239
util.write_file(python_version_path, current_python_version)
240240

241241

242+
def _should_bring_up_interfaces(init, args):
243+
if util.get_cfg_option_bool(init.cfg, 'disable_network_activation'):
244+
return False
245+
return not args.local
246+
247+
242248
def main_init(name, args):
243249
deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK]
244250
if args.local:
@@ -348,6 +354,7 @@ def main_init(name, args):
348354
util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net"))
349355

350356
# Stage 5
357+
bring_up_interfaces = _should_bring_up_interfaces(init, args)
351358
try:
352359
init.fetch(existing=existing)
353360
# if in network mode, and the datasource is local
@@ -367,7 +374,7 @@ def main_init(name, args):
367374
util.logexc(LOG, ("No instance datasource found!"
368375
" Likely bad things to come!"))
369376
if not args.force:
370-
init.apply_network_config(bring_up=not args.local)
377+
init.apply_network_config(bring_up=bring_up_interfaces)
371378
LOG.debug("[%s] Exiting without datasource", mode)
372379
if mode == sources.DSMODE_LOCAL:
373380
return (None, [])
@@ -388,7 +395,7 @@ def main_init(name, args):
388395
# dhcp clients to advertize this hostname to any DDNS services
389396
# LP: #1746455.
390397
_maybe_set_hostname(init, stage='local', retry_stage='network')
391-
init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL))
398+
init.apply_network_config(bring_up=bring_up_interfaces)
392399

393400
if mode == sources.DSMODE_LOCAL:
394401
if init.datasource.dsmode != mode:

cloudinit/cmd/tests/test_main.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import copy
55
import os
66
from io import StringIO
7+
from unittest import mock
8+
9+
import pytest
710

811
from cloudinit.cmd import main
912
from cloudinit import safeyaml
@@ -162,4 +165,24 @@ def set_hostname(name, cfg, cloud, log, args):
162165
for log in expected_logs:
163166
self.assertIn(log, self.stderr.getvalue())
164167

168+
169+
class TestShouldBringUpInterfaces:
170+
@pytest.mark.parametrize('cfg_disable,args_local,expected', [
171+
(True, True, False),
172+
(True, False, False),
173+
(False, True, False),
174+
(False, False, True),
175+
])
176+
def test_should_bring_up_interfaces(
177+
self, cfg_disable, args_local, expected
178+
):
179+
init = mock.Mock()
180+
init.cfg = {'disable_network_activation': cfg_disable}
181+
182+
args = mock.Mock()
183+
args.local = args_local
184+
185+
result = main._should_bring_up_interfaces(init, args)
186+
assert result == expected
187+
165188
# vi: ts=4 expandtab

cloudinit/distros/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,11 @@ def apply_network_config(self, netconfig, bring_up=False) -> bool:
227227

228228
# Now try to bring them up
229229
if bring_up:
230+
LOG.debug('Bringing up newly configured network interfaces')
230231
network_activator = activators.select_activator()
231232
network_activator.bring_up_all_interfaces(network_state)
233+
else:
234+
LOG.debug("Not bringing up newly configured network interfaces")
232235
return False
233236

234237
def apply_network_config_names(self, netconfig):

doc/rtd/topics/network-config.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ If `Cloud-init`_ 's networking config has not been disabled, and
7575
no other network information is found, then it will proceed
7676
to generate a fallback networking configuration.
7777

78+
Disabling Network Activation
79+
----------------------------
80+
81+
Some datasources may not be initialized until after network has been brought
82+
up. In this case, cloud-init will attempt to bring up the interfaces specified
83+
by the datasource metadata.
84+
85+
This behavior can be disabled in the cloud-init configuration dictionary,
86+
merged from ``/etc/cloud/cloud.cfg`` and ``/etc/cloud/cloud.cfg.d/*``::
87+
88+
disable_network_activation: true
7889

7990
Fallback Network Configuration
8091
==============================
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from tests.integration_tests.clouds import IntegrationCloud
4+
from tests.integration_tests.conftest import get_validated_source
5+
6+
7+
def _setup_custom_image(session_cloud: IntegrationCloud):
8+
"""Like `setup_image` in conftest.py, but with customized content."""
9+
source = get_validated_source(session_cloud)
10+
if not source.installs_new_version():
11+
return
12+
client = session_cloud.launch()
13+
14+
# Insert our "disable_network_activation" file here
15+
client.write_to_file(
16+
'/etc/cloud/cloud.cfg.d/99-disable-network-activation.cfg',
17+
'disable_network_activation: true\n',
18+
)
19+
20+
client.install_new_cloud_init(source)
21+
# Even if we're keeping instances, we don't want to keep this
22+
# one around as it was just for image creation
23+
client.destroy()
24+
25+
26+
# This test should be able to work on any cloud whose datasource specifies
27+
# a NETWORK dependency
28+
@pytest.mark.gce
29+
@pytest.mark.ubuntu # Because netplan
30+
def test_network_activation_disabled(session_cloud: IntegrationCloud):
31+
"""Test that the network is not activated during init mode."""
32+
_setup_custom_image(session_cloud)
33+
with session_cloud.launch() as client:
34+
result = client.execute('systemctl status google-guest-agent.service')
35+
if not result.ok:
36+
raise AssertionError('google-guest-agent is not active:\n%s',
37+
result.stdout)
38+
log = client.read_from_file('/var/log/cloud-init.log')
39+
40+
assert "Running command ['netplan', 'apply']" not in log
41+
42+
assert 'Not bringing up newly configured network interfaces' in log
43+
assert 'Bringing up newly configured network interfaces' not in log

0 commit comments

Comments
 (0)