Skip to content

Simplify shared external IP configuration type#10148

Merged
bnaecker merged 4 commits intomainfrom
ben/simplify-external-ip-config
Mar 26, 2026
Merged

Simplify shared external IP configuration type#10148
bnaecker merged 4 commits intomainfrom
ben/simplify-external-ip-config

Conversation

@bnaecker
Copy link
Collaborator

  • Changes the ExternalIpConfig type from a complicated enum to a struct with two optional fields. This lets us more simply and cleanly represent the IP stacks we support now, and also paves the way for supporting external IP configurations that have an Ephemeral or Floating IP, but without an SNAT address.
  • Keep the original type around in omicron common for backwards compatibility, using it in the previous versions of the sled-agent API. Add a new sled-agent API version and entrypoints for converting.
  • Removes a bunch of code that's now unnecessary, since we no longer need to enforce the complicated type's invariants. We can remove the custom deserialization logic and the entire ExternalIpConfigBuilder type.
  • Fixes ExternalIpConfig is too complicated #9839

- Changes the `ExternalIpConfig` type from a complicated enum to a
  struct with two optional fields. This lets us more simply and cleanly
  represent the IP stacks we support now, and also paves the way for
  supporting external IP configurations that have an Ephemeral or
  Floating IP, but without an SNAT address.
- Keep the original type around in omicron common for backwards
  compatibility, using it in the previous versions of the sled-agent
  API. Add a new sled-agent API version and entrypoints for converting.
- Removes a bunch of code that's now unnecessary, since we no longer
  need to enforce the complicated type's invariants. We can remove the
  custom deserialization logic and the entire `ExternalIpConfigBuilder`
  type.
- Fixes #9839
@bnaecker bnaecker requested a review from jgallagher March 25, 2026 15:46
Copy link
Contributor

@jgallagher jgallagher left a comment

Choose a reason for hiding this comment

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

This looks great - just a handful of questions / style nits.

#[serde(remote = "ExternalIps")]
struct ExternalIpsShadow<T>
// Private type only used for deriving the JSON schema for `ExternalIps`.
#[derive(JsonSchema)]
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like this is a dumb question but - why do we need this type / what's stopping us from deriving JsonSchema directly on ExternalIps?

Copy link
Collaborator Author

@bnaecker bnaecker Mar 26, 2026

Choose a reason for hiding this comment

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

Only the fact that the derived name of the schema is terrible. It's something like ExternalIps_for_Ipv4, or equally bad. I wanted the schema to have the same name as the type alias, and I wasn't sure how to do that otherwise.

.ipv6_config()
.v6
.as_ref()
.map(|v6| build_external_ipv6_config(Some(v6)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Tiny nit - could these two be

        let external_ips_v4 =
            build_external_ipv4_config(external_ips.v4.as_ref());
        let external_ips_v6 =
            build_external_ipv6_config(external_ips.v6.as_ref());

instead? At a glance that means they'd be non-optional-but-possibly-empty ExternalIpCfgs instead of Option<ExternalIpCfg>, but maybe that's a sign there's something else here that's a little off type wise? E.g., what's the difference between None and Some(external_ip_cfg) where external_ip_cfg is whatever's returned by build_external_ipvX_config(None)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There's not really a difference, but it makes the detection of a completely-empty configuration a bit easier. That said, I think I kept it most for historical reasons, and that it might be cleaner to remove it entirely. I'll either implement the suggestion you have, or clean it up more substantially. Thanks.

Copy link
Collaborator Author

@bnaecker bnaecker Mar 26, 2026

Choose a reason for hiding this comment

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

Ah, actually we can't quite do that. The fields of the the SetExternalIpsReq below are optional, so we need to return an actual Option<ExternalIpCfg<_>>. build_external_ipvX_config() returns an ExternalIpCfg<_> directly. I realize this is all kind of murky, but the short answer is: OPTE still has the same kind of representation as the one this PR removes. It uses Option<ExternalIpCfg>, where None means there is no IP stack of that version, and Some(_) means there is an IP stack, although it looks like it could be empty. The comment just above this block also describes this.

Hopefully that makes sense, and I'll try to clean this up as much as I can.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I left this as-is in c390700. I think it's about as clean as it can get already, and the mismatch here is between OPTE's and Omicron's mechanisms for representing "no IP stack of a particular version". LMK if you have other things you'd suggest here though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I didn't realize pushing this farther would require changes to OPTE. Yeah, agreed this is a perfectly reasonable stopping point.

- DCE
- Add some useful helpers
- Make list of floating IPs into a set. We have to add "frozen" versions
  of the original types to the older v1 to make this work, which is a
  little more code but worth it.
@bnaecker bnaecker requested a review from jgallagher March 26, 2026 04:49
.ipv6_config()
.v6
.as_ref()
.map(|v6| build_external_ipv6_config(Some(v6)));
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I didn't realize pushing this farther would require changes to OPTE. Yeah, agreed this is a perfectly reasonable stopping point.

@bnaecker bnaecker enabled auto-merge (squash) March 26, 2026 15:59
@bnaecker bnaecker merged commit a6bb64e into main Mar 26, 2026
16 checks passed
@bnaecker bnaecker deleted the ben/simplify-external-ip-config branch March 26, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ExternalIpConfig is too complicated

2 participants