Skip to content

feat(azure): add apply_network_config_set_name option to disable renames#6807

Open
cjp256 wants to merge 5 commits intocanonical:mainfrom
cjp256:set-name
Open

feat(azure): add apply_network_config_set_name option to disable renames#6807
cjp256 wants to merge 5 commits intocanonical:mainfrom
cjp256:set-name

Conversation

@cjp256
Copy link
Copy Markdown
Contributor

@cjp256 cjp256 commented Mar 27, 2026

Azure IMDS does not guarantee NIC ordering across reboots, which can cause interface name instability when using set-name to rename interfaces to ethX. Add a new datasource config option apply_network_config_set_name (default: True) that controls whether set-name directives are included in the generated network config.

When disabled, interfaces are identified by MAC address using the naming format nicXXXXXXXXXXXX (e.g., nic000d3a047598) instead of ethX in configuration. Without set-name, the kernel/udev are able to assign stable names.

Changes:

  • Add apply_network_config_set_name to BUILTIN_DS_CONFIG
  • Set default via _unpickle for backward compatibility with pickled state
  • Toggle netplan outputs per configuration
  • Update azure.rst documentation with IMDS reference link
  • Add parametrized unit tests for both enabled/disabled paths

Without this change:

$ grep rename /var/log/cloud-init.log
2026-03-27 23:36:15,195 - net[DEBUG]: Renamed [['7c:1e:52:cf:08:f2', 'eth0', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0b:28', 'eth1', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0d:8f', 'eth2', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:04:02', 'eth3', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:03:bf', 'eth4', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0c:c9', 'eth5', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:06:96', 'eth6', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0e:05', 'eth7', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0f:7c', 'eth8', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0a:c8', 'eth9', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0f:67', 'eth10', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:0f:d3', 'eth11', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:03:35', 'eth12', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:00:02', 'eth13', 'hv_netvsc', '0x3'], ['7c:1e:52:cf:06:43', 'eth14', 'hv_netvsc', '0x3']] with ops [('rename', '7c:1e:52:cf:0b:28', 'eth1', ('eth1', 'cirename0')), ('rename', '7c:1e:52:cf:0b:28', 'eth1', ('eth9', 'eth1')), ('rename', '7c:1e:52:cf:0d:8f', 'eth2', ('eth2', 'cirenam1')), ('rename', '7c:1e:52:cf:0d:8f', 'eth2', ('eth11', 'eth2')), ('rename', '7c:1e:52:cf:04:02', 'eth3', ('eth3', 'cirename2')), ('rename', '7c:1e:52:cf:04:02', 'eth3', ('eth6', 'eth3')), ('rename', '7c:1e:52:cf:03:bf', 'eth4', ('eth4', 'cirename3')), ('rename', '7c:1e:52:cf:03:bf', 'eth4', ('eth14', 'eth4')), ('rename', '7c:1e:52:cf:06:96', 'eth6', ('eth7', 'eth6')), ('rename', '7c:1e:52:cf:0e:05', 'eth7', ('eth13', 'eth7')), ('rename', '7c:1e:52:cf:0f:7c', 'eth8', ('eth8', 'cirename4')), ('rename', '7c:1e:52:cf:0f:7c', 'eth8', ('eth10', 'eth8')), ('rename', '7c:1e:52:cf:0a:c8', 'eth9', ('cirename1', 'eth9')), ('rename', '7c:1e:52:cf:0f:67', 'eth10', ('cirename3', 'eth10')), ('rename', '7c:1e:52:cf:0f:d3', 'eth11', ('cirename2', 'eth11')), ('rename', '7c:1e:52:cf:00:02', 'eth13', ('cirename0', 'eth13')), ('rename', '7c:1e:52:cf:06:43', 'eth14', ('cirename4', 'eth14'))]
2026-03-27 23:36:15,195 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'eth1', 'name', 'cirename0'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,198 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'eth2', 'name', 'cirename1'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,200 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'eth3', 'name', 'cirename2'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,202 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'eth4', 'name', 'cirename3'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,206 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'eth8', 'name', 'cirename4'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,208 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'cirename1', 'name', 'eth9'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,210 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'cirename3', 'name', 'eth10'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,211 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'cirename2', 'name', 'eth11'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,212 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'cirename0', 'name', 'eth13'] with allowed return codes [0] (shell=False, capture=True)
2026-03-27 23:36:15,213 - subp.py[DEBUG]: Running command ['ip', 'link', 'set', 'cirename4', 'name', 'eth14'] with allowed return codes [0] (shell=False, capture=True)

With this change:

$ grep rename /var/log/cloud-init.log
2026-03-27 23:29:47,160 - net[DEBUG]: no interfaces to rename
2026-03-27 23:29:48,199 - net[DEBUG]: no interfaces to rename
2026-03-27 23:33:31,312 - net[DEBUG]: no interfaces to rename
2026-03-27 23:33:31,544 - net[DEBUG]: no interfaces to rename

Azure IMDS does not guarantee NIC ordering across reboots, which can
cause interface name instability when using set-name to rename
interfaces to ethX. Add a new datasource config option
apply_network_config_set_name (default: True) that controls whether
set-name directives are included in the generated network config.

When disabled, interfaces are identified by MAC address using the
naming format nicXXXXXXXXXXXX (e.g., nic000d3a047598) instead of
ethX, allowing the kernel/udev to assign stable names.

Changes:
- Add apply_network_config_set_name to BUILTIN_DS_CONFIG
- Set default via _unpickle for backward compatibility with pickled state
- Toggle netplan outputs per configuration
- Update azure.rst documentation with IMDS reference link
- Add parametrized unit tests for both enabled/disabled paths

Signed-off-by: Chris Patterson <cpatterson@microsoft.com>
@github-actions github-actions Bot added the documentation This Pull Request changes documentation label Mar 27, 2026
Comment thread cloudinit/sources/DataSourceAzure.py Outdated
@cjp256 cjp256 marked this pull request as ready for review March 27, 2026 23:40
Comment thread tests/unittests/sources/test_azure.py Outdated
@github-actions
Copy link
Copy Markdown

Hello! Thank you for this proposed change to cloud-init. This pull request is now marked as stale as it has not seen any activity in 14 days. If no activity occurs within the next 7 days, this pull request will automatically close.

If you are waiting for code review and you are seeing this message, apologies! Please reply, tagging blackboxsw, and he will ensure that someone takes a look soon.

(If the pull request is closed and you would like to continue working on it, please do tag blackboxsw to reopen it.)

@github-actions github-actions Bot added the stale-pr Pull request is stale; will be auto-closed soon label Apr 14, 2026
@github-actions github-actions Bot closed this Apr 21, 2026
@blackboxsw blackboxsw removed the stale-pr Pull request is stale; will be auto-closed soon label Apr 21, 2026
@blackboxsw
Copy link
Copy Markdown
Collaborator

Nope. I should not have let this age out. Sorry @cjp256 . Will get review on this shortly.

@blackboxsw blackboxsw reopened this Apr 21, 2026
@cjp256
Copy link
Copy Markdown
Contributor Author

cjp256 commented May 5, 2026

@blackboxsw gentle ping, I know you're busy :D

Copy link
Copy Markdown
Collaborator

@blackboxsw blackboxsw left a comment

Choose a reason for hiding this comment

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

Test coverage, docs and behavior look good. I think we need a bit of a fix for unpickle handling and better test coverage there.

Additionally, if we can provide a set of logs establishing successfull behavior across reboot with set-name False that would be ideal. Thanks for the contributions Chris.

self._system_uuid = None
self._vm_id = None
self._wireserver_endpoint = DEFAULT_WIRESERVER_ENDPOINT
self.ds_cfg.setdefault(
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 coverage of unpickle case across upgrade path && system reboot.

  1. While we are here, shouldn't we also be setting apply_network_config_for_secondary_ips within _unpickle too to ensure those defaults under which the prior instance launch are preserved in the unpickled datasource?

  2. I think we also have a preexisting test coverage gap which should have alerted us to this potential _unpickle initialization issue w.r.t. apply_network_config_for_secondary_ips

--- a/tests/unittests/test_upgrade.py
+++ b/tests/unittests/test_upgrade.py
@@ -294,6 +294,8 @@ class TestUpgrade:
         missing_attrs = ds.__dict__.keys() - previous_obj_pkl.__dict__.keys()
         for attr in missing_attrs:
             assert attr in expected
+        missing_ds_cfg_attrs = ds.ds_cfg.keys() - previous_obj_pkl.ds_cfg.keys()
+        assert set() == missing_ds_cfg_attrs
 
     def test_networking_set_on_distro(self, previous_obj_pkl):
         """We always expect to have ``.networking`` on ``Distro`` objects."""

This test change above also found another disparity with experimental_skip_ready_report too.

Comment on lines 1624 to +1629
apply_network_config_for_secondary_ips=self.ds_cfg.get(
"apply_network_config_for_secondary_ips"
),
apply_network_config_set_name=self.ds_cfg.get(
"apply_network_config_set_name"
),
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.

Given that _unpickle and __init__ should properly set both apply_network_config_for_secondary_ips and apply_network_config_set_name values to defaults (or overrides in system config files), do we need to use ds_cfg.get or can we instead just use the return the expected value at the key index with self.ds_cfg["apply_network_config_set_name"]?

network_metadata: dict,
*,
apply_network_config_for_secondary_ips: bool,
apply_network_config_set_name: bool = True,
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.

I don't think we need a default value on this mandatory param as we are setting it in both __init__ and _unpickle.

Suggested change
apply_network_config_set_name: bool = True,
apply_network_config_set_name: bool,

if apply_network_config_set_name:
nicname = "eth{idx}".format(idx=idx)
else:
nicname = "enx{mac}".format(mac=mac.replace(":", ""))
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.

I confirm predictable interface naming convention prefix aligns with predictable naming expectations in systemd

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an Azure datasource configuration toggle to control whether generated network v2 config includes set-name directives, addressing NIC ordering instability in Azure IMDS that can cause ethX renames to change across reboots.

Changes:

  • Introduces apply_network_config_set_name (default True) in Azure datasource config and ensures backward compatibility for previously pickled datasource state.
  • Updates Azure IMDS network-config generation to optionally omit set-name and use MAC-derived identifiers when disabled.
  • Extends unit tests and updates Azure datasource documentation to describe the new option and motivation.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
cloudinit/sources/DataSourceAzure.py Adds new ds_cfg option and threads it into network-config generation to optionally omit set-name.
doc/rtd/reference/datasources/azure.rst Documents apply_network_config_set_name and explains the IMDS NIC ordering issue; updates example config.
tests/unittests/sources/test_azure.py Adds parametrized tests covering both enabled/disabled set-name paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread doc/rtd/reference/datasources/azure.rst Outdated
Comment on lines 78 to 87
An example configuration with the default values is provided below:

.. code-block:: yaml

datasource:
Azure:
apply_network_config: true
apply_network_config_for_secondary_ips: true
apply_network_config_set_name: false
data_dir: /var/lib/waagent
Comment on lines +2120 to +2124
mac = normalize_mac_address(intf["macAddress"])
if apply_network_config_set_name:
nicname = "eth{idx}".format(idx=idx)
else:
nicname = "enx{mac}".format(mac=mac.replace(":", ""))
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation This Pull Request changes documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants