Skip to content

feat(opennebula): support ETHx_ALIASn_IP/MASK for anycast addresses#6876

Open
mcanevet wants to merge 2 commits into
canonical:mainfrom
mcanevet:feat/opennebula-eth-aliases
Open

feat(opennebula): support ETHx_ALIASn_IP/MASK for anycast addresses#6876
mcanevet wants to merge 2 commits into
canonical:mainfrom
mcanevet:feat/opennebula-eth-aliases

Conversation

@mcanevet

Copy link
Copy Markdown
Contributor

Proposed Commit Message

```
feat(opennebula): support ETHx_ALIASn_IP/MASK for anycast addresses

OpenNebula's context-linux package supports per-NIC alias addresses
(ETHx_ALIAS0_IP, ETHx_ALIAS1_IP, …) used for anycast IPs. Add
get_alias_addresses() to OpenNebulaNetwork and wire it into gen_conf()
so alias addresses appear in the Netplan v2 output alongside the
primary address. Missing MASK defaults to /32.
```

Additional Context

OpenNebula allows attaching multiple IP addresses to a single NIC via
ETH<x>_ALIAS<n>_IP / ETH<x>_ALIAS<n>_MASK context variables. These
are typically used for anycast or VIP addresses. Previously cloud-init
ignored them, leaving the addresses unconfigured on the guest.

Test Steps

  1. In OpenNebula, add alias addresses to a NIC context:
    ```
    ETH0_ALIAS0_IP = "192.168.1.10"
    ETH0_ALIAS0_MASK = "255.255.255.0"
    ETH0_ALIAS1_IP = "192.168.1.11"
    ```
  2. Boot a cloud-init enabled VM using that context.
  3. Verify the alias addresses appear on the interface:
    ```
    ip addr show eth0

    expected: 192.168.1.10/24 and 192.168.1.11/32 present

    ```
  4. Verify a NIC without aliases is unaffected.

Merge type

  • Squash merge using "Proposed Commit Message"
  • Rebase and merge unique commits. Requires commit messages per-commit each referencing the pull request number (#<PR_NUM>)

Copilot AI review requested due to automatic review settings May 11, 2026 08:27
@github-actions github-actions Bot added the documentation This Pull Request changes documentation label May 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the OpenNebula datasource’s network configuration generation to include per-NIC alias (anycast/VIP) IPv4 addresses defined via ETH<x>_ALIAS<n>_IP / ETH<x>_ALIAS<n>_MASK, so they appear in the Netplan v2 addresses list alongside the primary address (defaulting missing masks to /32).

Changes:

  • Add OpenNebulaNetwork.get_alias_addresses() to collect alias IPv4 addresses from context.
  • Extend OpenNebulaNetwork.gen_conf() to append alias addresses into Netplan addresses.
  • Add unit tests and update OpenNebula datasource documentation for the new context variables.

Reviewed changes

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

File Description
cloudinit/sources/DataSourceOpenNebula.py Implements alias address parsing and includes aliases in generated Netplan output.
tests/unittests/sources/test_opennebula.py Adds unit tests covering alias parsing and integration into gen_conf().
doc/rtd/reference/datasources/opennebula.rst Documents ETH<x>_ALIAS<n>_IP/MASK behavior and default mask handling.

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

Comment thread cloudinit/sources/DataSourceOpenNebula.py Outdated
@mcanevet
mcanevet force-pushed the feat/opennebula-eth-aliases branch from 23811d2 to 16aff3a Compare May 11, 2026 08:54

@blackboxsw blackboxsw left a comment

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.

Thank you again @mcanevet for this proposal. I think we might want a bit more logging verbosity when cloud-init ignores potential invalid config values such as gap ranges in aliases. Silently ignoring potential config is not ideal.

Can you please provide the representative /etc/netplan/50-cloud-init.yaml created and cloud-init status --format=yaml on a vm with this context provided if possible?

Comment thread tests/unittests/sources/test_opennebula.py
Comment thread cloudinit/sources/DataSourceOpenNebula.py
@blackboxsw

Copy link
Copy Markdown
Collaborator

Thanks for this PR, I can confirm the documented behavior and contextualization switches documented in opennebula 7.2 operations guide](https://docs.opennebula.io/7.2/product/operation_references/configuration_references/template/#template-context)

@blackboxsw blackboxsw self-assigned this Jul 8, 2026
mcanevet added 2 commits July 9, 2026 08:34
OpenNebula's context-linux package supports per-NIC alias addresses
(ETHx_ALIAS0_IP, ETHx_ALIAS1_IP, …) used for anycast IPs. Add
get_alias_addresses() to OpenNebulaNetwork and wire it into gen_conf()
so alias addresses appear in the Netplan v2 output alongside the
primary address. Missing MASK defaults to /32.
get_alias_addresses() silently ignored any alias entries beyond a gap
in the index sequence (e.g. ALIAS0 and ALIAS2 present but ALIAS1
missing), giving no indication that context was likely misconfigured.
Now log a warning naming the skipped keys.
@mcanevet
mcanevet force-pushed the feat/opennebula-eth-aliases branch from 16aff3a to 2373576 Compare July 9, 2026 06:54
@mcanevet

mcanevet commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@blackboxsw fixed

@mcanevet
mcanevet requested a review from blackboxsw July 9, 2026 15:38

@blackboxsw blackboxsw left a comment

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.

Thank you for the swift updates @mcanevet. I'm wondering if these is a better way to simplify the separate ordered looping when detecting gaps. Can we consolidate some of that looping somehow? Given that you are using a regex below, that regex could help in a for loop instead of just using a while loop and an incrementing idx to encounter a a gap in self.context.

Comment on lines +322 to +323
"Ignoring %s: found gap at %s%d_IP",
", ".join("%s%d_IP" % (prefix, i) for i in skipped),

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.

gap doesn't provide enough context in the message. Let's be a bit more explicit about the problem.

Ignoring network config keys %s due to missing %s

Comment on lines +303 to +319
idx = 0
while True:
ip_key = "%s%d_IP" % (prefix, idx)
ip = self.context.get(ip_key)
if not ip:
break
mask_key = "%s%d_MASK" % (prefix, idx)
mask = self.context.get(mask_key) or "255.255.255.255"
net_prefix = str(net.ipv4_mask_to_net_prefix(mask))
aliases.append("%s/%s" % (ip, net_prefix))
idx += 1

key_re = re.compile(r"^%s(\d+)_IP$" % re.escape(prefix))
skipped = sorted(
int(m.group(1)) for m in map(key_re.match, self.context) if m
)
skipped = [i for i in skipped if i >= idx]

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.

It feels a bit like we are processing the data in self.context twice through two separate loops which makes it a teeny bit harder to maintain when we look at this in the future.

Could we instead try processing these keys once?
something like (completely untested):

key_re = re.compile(rf"^{c_dev.upper()}_ALIAS(?P<alias_idx>\d+)_IP$" % re.escape(prefix))
for key, value  in sorted(self.content.items()):
    match = key_re.match(key)
    if not match
        continue
    if int(match['alias_idx']) == idx:
        idx += 1
        mask_key = self.context.get(key.replace("IP", "MASK"))
        mask = self.context.key(mask_key) or "255.255.255.255"
        aliases.append(f"{value}/{mask}")
    else:
        skipped.append(key)

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