Skip to content

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

Merged
jujubot merged 2 commits intojuju:4.0from
iyiguncevik:fix-ip-address-sort
Mar 31, 2026
Merged

fix(network): make address selection deterministic across layers#22043
jujubot merged 2 commits intojuju:4.0from
iyiguncevik:fix-ip-address-sort

Conversation

@iyiguncevik
Copy link
Copy Markdown
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. domain/network/state/unitinfo.go + domain/network/service/unitinfo.go —no change. This was fixed in fix: filter virtual ethernet devices for relation networks #22054

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

  3. 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/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

@jujubot jujubot added the 4.0 label Mar 19, 2026
@iyiguncevik iyiguncevik force-pushed the fix-ip-address-sort branch 7 times, most recently from 63fe274 to 7af92a9 Compare March 26, 2026 11:59
Copy link
Copy Markdown
Member

@manadart manadart left a comment

Choose a reason for hiding this comment

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

One change.

Comment thread domain/network/service/unitaddress.go Outdated
@iyiguncevik iyiguncevik requested a review from manadart March 27, 2026 15:05
Copy link
Copy Markdown
Member

@manadart manadart left a comment

Choose a reason for hiding this comment

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

Thanks.

Based on how things are going, this logic is probably on borrowed time, but the change is correct for now.

Copy link
Copy Markdown
Member

@nvinuesa nvinuesa left a comment

Choose a reason for hiding this comment

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

LGTM, good test coverage, thanks.

Comment on lines +45 to +47
// AllMatchingScope already falls back to public addresses if no
// cloud-local address exists. If we still have no match, only
// unsuitable addresses (localhost, link-local) remain.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As @manadart mentioned, this will probably follow the same kind of implementation as GetUnitPublicAddresses() (i.e. handle the matching/sorting on the state layer with proper SQL).

@iyiguncevik iyiguncevik force-pushed the fix-ip-address-sort branch from 992532a to 435893b Compare March 31, 2026 12:31
@iyiguncevik
Copy link
Copy Markdown
Contributor Author

/merge

@jujubot jujubot merged commit 1424133 into juju:4.0 Mar 31, 2026
23 of 26 checks passed
@iyiguncevik iyiguncevik deleted the fix-ip-address-sort branch March 31, 2026 14:00
jujubot added a commit that referenced this pull request Apr 2, 2026
#22139

## Merged PR's
- #22122 @SimonRichardson
- #22123 @jack-w-shaw
- #22124 @SimonRichardson
- #22125 @SimonRichardson
- #22131 @tonyandrewmeyer
- #22117 @gfouillet/v4.0/fix
- #22090 @hmlanigan
- #22087 @tmihoc
- #22116 @jack-w-shaw
- #22102 @SimonRichardson
- #22043 @iyiguncevik
- #22086 @SimonRichardson
- #22047 @nvinuesa
- #21848 @nvinuesa
- #22103 @nvinuesa
- #21352 @hpidcock
- #22109 @kian99
- #22093 @gfouillet/v4.0/ci
- #21834 @manadart
- #22036 @nvinuesa
- #22079 @manadart
- #22046 @SimonRichardson
- #22029 @SimonRichardson
- #22055 @tmihoc
- #22085 @SimonRichardson
- #21928 @hpidcock
- #22054 @manadart
- #22082 @jack-w-shaw
- #22068 @jack-w-shaw
- #22025 @hmlanigan
- #22045 @gfouillet/v4.0/fix
- #22049 @wallyworld
- #21985 @anvial
- #22028 @hmlanigan
- #22031 @gfouillet/v4.0/fix
- #22022 @gfouillet/v4.0/fix
- #22008 @nvinuesa
- #22007 @jonathan-conder
- #22027 @gfouillet/v4.0/fix
- #21725 @hpidcock
- #22005 @wallyworld
- #21981 @tlm
- #21968 @anvial
- #21987 @manadart
- #22014 @gfouillet/v4.0/fix
- #22011 @gfouillet/v4.0/fix
- #21960 @tlm
- #21918 @manadart
- #21925 @hmlanigan
- #22009 @manadart
- #21880 @manadart
- #22010 @jack-w-shaw
- #22001 @jack-w-shaw
- #21982 @wallyworld


## Conflicts
- `apiserver/facades/client/applicationoffers/applicationoffers_test.go`
- `core/version/version.go`
- `domain/schema/controller.go`
- `domain/schema/model.go`
- `scripts/win-installer/setup.iss`
- `snap/snapcraft.yaml`

## Patches
- Controller / `0029-tracing.PATCH.sql`
- Model / `0052-resource.PATCH.sql`
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.

4 participants