Skip to content

fix(network): make address selection deterministic across layers#22043

Open
iyiguncevik wants to merge 3 commits intojuju:4.0from
iyiguncevik:fix-ip-address-sort
Open

fix(network): make address selection deterministic across layers#22043
iyiguncevik wants to merge 3 commits intojuju:4.0from
iyiguncevik:fix-ip-address-sort

Conversation

@iyiguncevik
Copy link
Contributor

@iyiguncevik iyiguncevik commented Mar 19, 2026

This PR fixes non-deterministic address selection by making scope-based
matching deterministic and by clarifying layer responsibilities between network
state and service code.

The issue is that callers often select the first matching address; when
candidate order is unstable, selected addresses can vary across runs.

What changed:

  1. core/network/address.go: Updated SpaceAddresses.AllMatchingScope to return deterministically sorted matches —this is the most important change.

  2. domain/network/service/unitaddress.go: GetUnitPrivateAddress now:

  • returns first cloud-local match when available;
  • otherwise sorts all candidates and returns a deterministic fallback.
  1. domain/network/state/unitinfo.go + domain/network/service/unitinfo.go:
  • State no longer pre-filters ingress addresses to cloud-local.
  • Service now applies cloud-local selection/filtering for endpoint/network responses.
  • This keeps policy in service layer and preserves full candidates in state.
  1. domain/status/state/model/modelstate.go —no change. It is intentionally left unchanged in this PR because GetMachineFullStatuses currently has downstream expectations on that shape/contract, so “fixing” it here would break expected behavior.

Checklist

  • Code style: imports ordered, good names, simple structure, etc
  • Comments saying why design decisions were made
  • Go unit tests, with comments saying what you're testing
  • Integration tests, with comments saying what you're testing
  • doc.go added or updated in changed packages

QA steps

Setup network interfaces:

❯ lxc network create lxdbr1 \
  ipv4.address=10.155.6.1/24 \
  ipv4.nat=true \
  ipv6.address=none
❯ lxc network create lxdbr2 \
  ipv4.address=10.155.2.1/24 \
  ipv4.nat=true \
  ipv6.address=none
❯ lxc network create lxdbr3 \
  ipv4.address=10.155.3.1/24 \
  ipv4.nat=true \
  ipv6.address=none
    
❯ lxc network list

❯ lxc profile device add default eth1 nic \
  network=lxdbr1 name=eth1
❯ lxc profile device add default eth2 nic \
  network=lxdbr2 name=eth2
❯ lxc profile device add default eth3 nic \
  network=lxdbr3 name=eth3

❯ lxc profile show default | yq '.devices'
eth0:
  name: eth0
  network: lxdbr0
  type: nic
eth1:
  name: eth1
  network: lxdbr1
  type: nic
eth2:
  name: eth2
  network: lxdbr2
  type: nic
eth3:
  name: eth3
  network: lxdbr3
  type: nic
root:
  path: /
  pool: default
  type: disk

❯ juju bootstrap lxd src

Verify IP addresses

❯ juju show-machine -m controller 0 | yq '.machines["0"].network-interfaces'
eth0:
  ip-addresses:
    - 10.179.172.166/24
    - fd42:4d03:34fb:4185:216:3eff:febc:bb6b/64
  mac-address: 00:16:3e:bc:bb:6b
  gateway: 10.179.172.1
  space: alpha
  is-up: true
eth1:
  ip-addresses:
    - 10.155.6.24/24
  mac-address: 00:16:3e:2f:35:f3
  space: alpha
  is-up: true
eth2:
  ip-addresses:
    - 10.155.2.157/24
  mac-address: 00:16:3e:17:73:e9
  space: alpha
  is-up: true
eth3:
  ip-addresses:
    - 10.155.3.114/24
  mac-address: 00:16:3e:49:12:d9
  space: alpha
  is-up: true

❯ juju add-model m
❯ juju add-ssh-key "$(cat ${JUJU_DATA}/ssh/juju_id_ed25519.pub)"
❯ juju deploy ubuntu-lite qa

❯ juju ssh 0 'ip -4 addr' | grep inet
    inet 127.0.0.1/8 scope host lo
    inet 10.179.172.74/24 metric 100 brd 10.179.172.255 scope global dynamic eth0
    inet 10.155.6.93/24 metric 110 brd 10.155.6.255 scope global dynamic eth1
    inet 10.155.2.190/24 metric 120 brd 10.155.2.255 scope global dynamic eth2
    inet 10.155.3.238/24 metric 130 brd 10.155.3.255 scope global dynamic eth3

❯ juju show-unit qa/0 --format=yaml | yq '."qa/0".public-address'
10.155.2.190

❯ juju exec --unit qa/0 -- unit-get private-address
10.155.2.190                                                  

❯ juju exec --unit qa/0 -- unit-get public-address
10.155.2.190

❯ juju status --format=json | jq -r '.machines["0"]."ip-addresses"[]'
10.155.2.190
10.155.3.238
10.155.6.93
10.179.172.74
fd42:4d03:34fb:4185:216:3eff:fe5e:e9ef

# try 100 timesfor i in {1..100}; do juju status --format=json | jq -r '.machines["0"]."ip-addresses"[]'; done | paste - - - - - | sort | uniq -c
    100 10.155.2.190	10.155.3.238	10.155.6.93	10.179.172.74	fd42:4d03:34fb:4185:216:3eff:fe5e:e9ef 

Documentation changes

Links

Issue: Fixes #21809

Jira card: JUJU-9244

@iyiguncevik iyiguncevik force-pushed the fix-ip-address-sort branch from f291c1c to fa252d2 Compare March 19, 2026 17:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants