Skip to content

IPClaim should not carry spec.ipFamily; the class owns the family #128

Closed

Description

@scotwells

Problem

IPClaim.spec.ipFamily duplicates a fact the class already owns, and the duplication is doing damage in both directions.

When spec.className is set, the field is silently ignored. ResolveClass returns LoadClass(className) without looking at the family, and everything downstream — the prefix request, the IPAllocation — reads class.Spec.IPFamily. A claim saying className: datum-vpc-ipv6, ipFamily: IPv4 allocates IPv6 and reports no error.

When it is absent, it corrupts validation. validateIPClaim sizes the prefix-length bound off the claim's own family:

maxLen := int32(32)
if c.Spec.IPFamily == ipam.IPv6 { maxLen = 128 }

A claim that names an IPv6 class, omits ipFamily, and asks for prefixLength: 64 is rejected with "must be between 1 and 32". The class knows it is IPv6; validation cannot ask, because strategy validation runs before class resolution and cannot read the database.

The data already agrees with the design: of 478 IPClasses in production, 474 have an empty spec.ipFamily. Those are the per-project references; only the 4 platform classes declare a family, resolved through spec.source.

Proposed change

Make spec.className required and remove spec.ipFamily from IPClaim.

  • Move the prefix-length range check out of strategy validation into Create, where the class is already resolved and spec.allowedPrefixLengths is checked. Validation then uses the authoritative family.
  • LoadDefaultClass and IsDefaultClassAnnotation go with it.
  • Subsumes the idea of adding status.classRef: if every claim names its class, there is nothing resolved to report.

Open question

Six claims in production currently use the family-only path, all created this week (*-0-eth0-f-ipv6). NSO sets ClassName: request.className when it builds claims, so either that value is empty on the endpoint path or a different producer is responsible. Tracing this is a prerequisite: if the producer genuinely cannot know class names, the answer may be a named default class per project rather than removing the selector outright.

Breaking change to the v1alpha1 surface.

Activity

  1. scotwells commented on Sep 23, 2026

    @scotwells
    ContributorAuthor

    Producer traced: this is the default path, not a stray caller

    The family-only claims come from NSO's networkinterfaceclaim_controller.go, and deliberately:

    if request.className == "" {
        ipClaim.Spec.IPFamily = ipamv1alpha1.IPFamily(request.family)
    }

    request.className is empty for every request built from NetworkInterfaceClaim.spec.ipFamilies:

    for _, family := range claim.Spec.IPFamilies {
        requests = append(requests, allocationRequest{
            discriminator: familyDiscriminator(family),
            family:        family,
        })
    }

    That field carries +kubebuilder:default={IPv6}, so every NetworkInterfaceClaim requests an address by family unless someone explicitly adds spec.addresses[].class — and that path is for extra addresses, flagged external: true. The six production claims are not stragglers; they are the currently-live interfaces. Their names confirm it: familyDiscriminator renders f-ipv6, classDiscriminator renders c-<class>, and all six end in -f-ipv6.

    Revised proposal

    Removing spec.ipFamily would break the primary address path for every workload on the platform. Withdrawing that part.

    The two defects are real and fixable without touching the contract:

    1. className and ipFamily are not mutually exclusive. A claim setting both has one silently ignored. Reject it at validation instead, so the field cannot contradict the class.
    2. Prefix-length validation reads the wrong family. Move the range check out of strategy validation into Create, where the class is resolved, so the bound comes from the class rather than defaulting to IPv4 when the claim says nothing.

    And status.classRef is now clearly worth adding rather than optional: the family-only path is the default path, so the resolved class is unrecorded on the majority of claims, not a handful.

    Retitling would be fair — this is no longer about removing the field.

  2. scotwells commented on Sep 23, 2026

    @scotwells
    ContributorAuthor

    Implemented in #129, as the three fixes above minus the removal: spec.className is defaulted from the resolved class at create (PVC/StorageClass shape), a family that disagrees with the class is refused rather than ignored, and the IPv4-assuming prefix-length bound is gone. spec.ipFamily stays as the selector the defaulting needs, and status.classRef is no longer needed — the answer lands in spec.

  3. scotwells commented on Sep 23, 2026

    @scotwells
    ContributorAuthor

    Superseded in direction by #130: the per-family default selector has never selected anything (zero IPv4 classes, claims or pools in either environment), so the follow-on is one default class per project and no spec.ipFamily on IPClaim at all. #129 still stands on its own — defaulting spec.className and the prefix-length bound are correct under either model, and refusing a mismatched family is the right guard until #130 lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions