Skip to content
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
11 changes: 9 additions & 2 deletions cloudinit/cmd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ def purge_cache_on_python_version_change(init):
util.write_file(python_version_path, current_python_version)


def _should_bring_up_interfaces(init, args):
if util.get_cfg_option_bool(init.cfg, 'disable_network_activation'):
return False
return not args.local


def main_init(name, args):
deps = [sources.DEP_FILESYSTEM, sources.DEP_NETWORK]
if args.local:
Expand Down Expand Up @@ -348,6 +354,7 @@ def main_init(name, args):
util.del_file(os.path.join(path_helper.get_cpath("data"), "no-net"))

# Stage 5
bring_up_interfaces = _should_bring_up_interfaces(init, args)
try:
init.fetch(existing=existing)
# if in network mode, and the datasource is local
Expand All @@ -367,7 +374,7 @@ def main_init(name, args):
util.logexc(LOG, ("No instance datasource found!"
" Likely bad things to come!"))
if not args.force:
init.apply_network_config(bring_up=not args.local)
init.apply_network_config(bring_up=bring_up_interfaces)
LOG.debug("[%s] Exiting without datasource", mode)
if mode == sources.DSMODE_LOCAL:
return (None, [])
Expand All @@ -388,7 +395,7 @@ def main_init(name, args):
# dhcp clients to advertize this hostname to any DDNS services
# LP: #1746455.
_maybe_set_hostname(init, stage='local', retry_stage='network')
init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL))
init.apply_network_config(bring_up=bring_up_interfaces)

if mode == sources.DSMODE_LOCAL:
if init.datasource.dsmode != mode:
Expand Down
23 changes: 23 additions & 0 deletions cloudinit/cmd/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import copy
import os
from io import StringIO
from unittest import mock

import pytest

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


class TestShouldBringUpInterfaces:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the test coverage here. It's functional for the additional function created. Since main isn't covered well with unit tests, this makes things tough to instrument unit tests for this interaction so I think its fine we rely on the integration test here.

@pytest.mark.parametrize('cfg_disable,args_local,expected', [
(True, True, False),
(True, False, False),
(False, True, False),
(False, False, True),
])
def test_should_bring_up_interfaces(
self, cfg_disable, args_local, expected
):
init = mock.Mock()
init.cfg = {'disable_network_activation': cfg_disable}

args = mock.Mock()
args.local = args_local

result = main._should_bring_up_interfaces(init, args)
assert result == expected

# vi: ts=4 expandtab
3 changes: 3 additions & 0 deletions cloudinit/distros/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,11 @@ def apply_network_config(self, netconfig, bring_up=False) -> bool:

# Now try to bring them up
if bring_up:
LOG.debug('Bringing up newly configured network interfaces')
network_activator = activators.select_activator()
network_activator.bring_up_all_interfaces(network_state)
else:
LOG.debug("Not bringing up newly configured network interfaces")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good log tracers here for quick triage in failure cases if needed.

return False

def apply_network_config_names(self, netconfig):
Expand Down
11 changes: 11 additions & 0 deletions doc/rtd/topics/network-config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ If `Cloud-init`_ 's networking config has not been disabled, and
no other network information is found, then it will proceed
to generate a fallback networking configuration.

Disabling Network Activation
----------------------------

Some datasources may not be initialized until after network has been brought
up. In this case, cloud-init will attempt to bring up the interfaces specified
by the datasource metadata.

This behavior can be disabled in the cloud-init configuration dictionary,
merged from ``/etc/cloud/cloud.cfg`` and ``/etc/cloud/cloud.cfg.d/*``::

disable_network_activation: true

Fallback Network Configuration
==============================
Expand Down
43 changes: 43 additions & 0 deletions tests/integration_tests/datasources/test_network_dependency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import pytest

from tests.integration_tests.clouds import IntegrationCloud
from tests.integration_tests.conftest import get_validated_source


def _setup_custom_image(session_cloud: IntegrationCloud):
"""Like `setup_image` in conftest.py, but with customized content."""
source = get_validated_source(session_cloud)
if not source.installs_new_version():
return
client = session_cloud.launch()

# Insert our "disable_network_activation" file here
client.write_to_file(
'/etc/cloud/cloud.cfg.d/99-disable-network-activation.cfg',
'disable_network_activation: true\n',
)

client.install_new_cloud_init(source)
# Even if we're keeping instances, we don't want to keep this
# one around as it was just for image creation
client.destroy()


# This test should be able to work on any cloud whose datasource specifies
# a NETWORK dependency
@pytest.mark.gce
@pytest.mark.ubuntu # Because netplan
def test_network_activation_disabled(session_cloud: IntegrationCloud):
"""Test that the network is not activated during init mode."""
_setup_custom_image(session_cloud)
with session_cloud.launch() as client:
result = client.execute('systemctl status google-guest-agent.service')
if not result.ok:
raise AssertionError('google-guest-agent is not active:\n%s',
result.stdout)
log = client.read_from_file('/var/log/cloud-init.log')

assert "Running command ['netplan', 'apply']" not in log

assert 'Not bringing up newly configured network interfaces' in log
assert 'Bringing up newly configured network interfaces' not in log