diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6faccd2..b691d7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,6 +110,124 @@ jobs: echo " ok $f" done + e2e: + name: End-to-end tests (Chainsaw) + runs-on: ubuntu-latest + env: + TASK_X_REMOTE_TASKFILES: "1" + # test-infra writes kubeconfig here when running outside the test-infra repo + KUBECONFIG: .test-infra/kubeconfig + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install kind + run: | + KIND_VERSION="v0.30.0" + curl -fsSL -o kind "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/kind-linux-amd64" + chmod +x kind + sudo mv kind /usr/local/bin/kind + + - name: Install kubectl + run: | + KUBECTL_VERSION="$(curl -sL https://dl.k8s.io/release/stable.txt)" + curl -fsSL -o kubectl "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" + chmod +x kubectl + sudo mv kubectl /usr/local/bin/kubectl + + - name: Install kustomize + run: | + curl -sLo /tmp/kustomize.tgz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.3/kustomize_v5.4.3_linux_amd64.tar.gz + tar -xzf /tmp/kustomize.tgz -C /tmp + sudo mv /tmp/kustomize /usr/local/bin/ + + - name: Install Helm + run: | + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + + - name: Install Flux CLI + run: | + curl -fsSL https://fluxcd.io/install.sh | sudo bash + + - name: Install Chainsaw + run: | + CHAINSAW_VERSION="v0.2.12" + curl -fsSL -o /tmp/chainsaw.tgz \ + "https://github.com/kyverno/chainsaw/releases/download/${CHAINSAW_VERSION}/chainsaw_linux_amd64.tar.gz" + tar -xzf /tmp/chainsaw.tgz -C /tmp chainsaw + sudo mv /tmp/chainsaw /usr/local/bin/chainsaw + + - name: Spin up kind cluster (test-infra) + run: task --yes test-infra:cluster-up + + - name: Build IPAM container image + run: task --yes dev:build + + - name: Load image into kind + run: task --yes dev:load + + - name: Create control-plane-ca configmap + run: | + # The aggregated apiserver uses --requestheader-client-ca-file to verify + # the front proxy identity. In kind the cert lives in the + # extension-apiserver-authentication ConfigMap in kube-system. + kubectl create namespace ipam-system --dry-run=client -o yaml | kubectl apply -f - + kubectl get configmap extension-apiserver-authentication -n kube-system \ + -o jsonpath='{.data.requestheader-client-ca-file}' > /tmp/requestheader-ca.crt + kubectl create configmap control-plane-ca \ + -n ipam-system \ + --from-file=ca.crt=/tmp/requestheader-ca.crt \ + --dry-run=client -o yaml | kubectl apply -f - + + - name: Deploy IPAM service + run: | + kubectl apply -k config/overlays/test-infra + # Wait for cert-manager to issue the TLS secret before the apiserver pods can mount it. + kubectl -n ipam-system wait certificate/ipam-tls \ + --for=condition=Ready --timeout=120s + kubectl -n ipam-system wait helmrelease/postgres \ + --for=condition=Ready --timeout=300s + kubectl -n ipam-system wait pod \ + -l app.kubernetes.io/name=postgresql \ + --for=condition=Ready --timeout=180s + kubectl wait --for=condition=Ready pod \ + -l app=ipam-apiserver -n ipam-system --timeout=180s + kubectl wait --for=condition=Available \ + apiservice/v1alpha1.ipam.miloapis.com --timeout=180s + + - name: Run Chainsaw e2e suites + run: chainsaw test test/e2e/ + + - name: Dump diagnostics on failure + if: failure() + run: | + echo "=== Pods ===" + kubectl get pods -A + echo "=== IPAM pod describe ===" + kubectl describe pods -n ipam-system -l app=ipam-apiserver || true + echo "=== IPAM apiserver logs ===" + kubectl logs -n ipam-system -l app=ipam-apiserver --all-containers --tail=100 || true + echo "=== CertificateRequests ===" + kubectl get certificaterequests -n ipam-system -o wide || true + echo "=== Events ===" + kubectl get events -n ipam-system --sort-by='.lastTimestamp' | tail -60 || true + echo "=== APIService ===" + kubectl get apiservice v1alpha1.ipam.miloapis.com -o yaml || true + + - name: Tear down kind cluster + if: always() + run: task --yes test-infra:cluster-down + observability: name: Verify observability artifacts runs-on: ubuntu-latest diff --git a/config/base/deployment.yaml b/config/base/deployment.yaml index 15e32ef..5ad21df 100644 --- a/config/base/deployment.yaml +++ b/config/base/deployment.yaml @@ -54,7 +54,7 @@ spec: type: RuntimeDefault initContainers: - name: migrate - image: ghcr.io/datum-cloud/ipam-apiserver:latest + image: ghcr.io/milo-os/ipam:latest imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false @@ -86,7 +86,7 @@ spec: memory: 256Mi containers: - name: apiserver - image: ghcr.io/datum-cloud/ipam-apiserver:latest + image: ghcr.io/milo-os/ipam:latest imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false @@ -111,6 +111,7 @@ spec: - --authentication-skip-lookup=$(AUTHENTICATION_SKIP_LOOKUP) - --authentication-tolerate-lookup-failure=$(AUTHENTICATION_TOLERATE_LOOKUP_FAILURE) - --authorization-always-allow-paths=$(AUTHORIZATION_ALWAYS_ALLOW_PATHS) + - --requestheader-client-ca-file=/etc/kubernetes/pki/requestheader/ca.crt - --requestheader-username-headers=$(REQUESTHEADER_USERNAME_HEADERS) - --requestheader-group-headers=$(REQUESTHEADER_GROUP_HEADERS) - --requestheader-uid-headers=$(REQUESTHEADER_UID_HEADERS) diff --git a/config/base/kustomization.yaml b/config/base/kustomization.yaml index d14bc61..c254f2e 100644 --- a/config/base/kustomization.yaml +++ b/config/base/kustomization.yaml @@ -24,7 +24,7 @@ labels: # Image (overlays override newTag for different environments) images: - - name: ghcr.io/datum-cloud/ipam-apiserver + - name: ghcr.io/milo-os/ipam newTag: latest # rbac-auth-reader RoleBinding belongs in kube-system. The patch keeps the diff --git a/config/overlays/dev/kustomization.yaml b/config/overlays/dev/kustomization.yaml index cf2f205..95cc285 100644 --- a/config/overlays/dev/kustomization.yaml +++ b/config/overlays/dev/kustomization.yaml @@ -16,7 +16,7 @@ components: - ../../components/observability images: - - name: ghcr.io/datum-cloud/ipam-apiserver + - name: ghcr.io/milo-os/ipam newName: ipam-apiserver newTag: dev diff --git a/config/overlays/test-infra/anonymous-rbac.yaml b/config/overlays/test-infra/anonymous-rbac.yaml new file mode 100644 index 0000000..a0cf704 --- /dev/null +++ b/config/overlays/test-infra/anonymous-rbac.yaml @@ -0,0 +1,15 @@ +--- +# Dev-only: grant anonymous users read access via the apiservice so kubectl +# proxy / curl loops work without bearer tokens. Do NOT apply outside dev. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: ipam-dev-anonymous +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - apiGroup: rbac.authorization.k8s.io + kind: User + name: system:anonymous diff --git a/config/overlays/test-infra/kustomization.yaml b/config/overlays/test-infra/kustomization.yaml index 3d7254c..1108888 100644 --- a/config/overlays/test-infra/kustomization.yaml +++ b/config/overlays/test-infra/kustomization.yaml @@ -5,20 +5,30 @@ namespace: ipam-system resources: - ../../base + - secret.yaml + - anonymous-rbac.yaml + - tls-certificate.yaml components: - ../../components/namespace - ../../components/api-registration - - ../../components/cert-manager-ca - ../../components/postgres images: - - name: ghcr.io/datum-cloud/ipam-apiserver + - name: ghcr.io/milo-os/ipam newName: ipam-apiserver newTag: dev patches: - path: patches/apiservice-patch.yaml + - path: patches/deployment-patch.yaml + target: + kind: Deployment + name: ipam-apiserver + - path: patches/tls-volume-patch.yaml + target: + kind: Deployment + name: ipam-apiserver labels: - includeSelectors: false diff --git a/config/overlays/test-infra/patches/deployment-patch.yaml b/config/overlays/test-infra/patches/deployment-patch.yaml new file mode 100644 index 0000000..06af168 --- /dev/null +++ b/config/overlays/test-infra/patches/deployment-patch.yaml @@ -0,0 +1,18 @@ +--- +# In kind, images are loaded via `kind load docker-image` and are never +# pulled from a registry. Set Never to prevent Kyverno or other admission +# controllers from mutating imagePullPolicy to Always, which would fail +# because ipam-apiserver:dev doesn't exist on any public registry. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ipam-apiserver +spec: + template: + spec: + initContainers: + - name: migrate + imagePullPolicy: Never + containers: + - name: apiserver + imagePullPolicy: Never diff --git a/config/overlays/test-infra/patches/tls-volume-patch.yaml b/config/overlays/test-infra/patches/tls-volume-patch.yaml new file mode 100644 index 0000000..3436058 --- /dev/null +++ b/config/overlays/test-infra/patches/tls-volume-patch.yaml @@ -0,0 +1,6 @@ +- op: replace + path: /spec/template/spec/volumes/0 + value: + name: tls-certs + secret: + secretName: ipam-tls diff --git a/config/overlays/test-infra/secret.yaml b/config/overlays/test-infra/secret.yaml new file mode 100644 index 0000000..86eabc2 --- /dev/null +++ b/config/overlays/test-infra/secret.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-credentials + namespace: ipam-system + labels: + app.kubernetes.io/name: postgres + app.kubernetes.io/component: database + app.kubernetes.io/part-of: ipam.miloapis.com +type: Opaque +stringData: + dsn: "postgres://ipam:devpassword@postgres-postgresql.ipam-system.svc.cluster.local:5432/ipam?sslmode=disable" + password: "devpassword" diff --git a/config/overlays/test-infra/tls-certificate.yaml b/config/overlays/test-infra/tls-certificate.yaml new file mode 100644 index 0000000..8ee6234 --- /dev/null +++ b/config/overlays/test-infra/tls-certificate.yaml @@ -0,0 +1,22 @@ +--- +# cert-manager Certificate (not CSI driver) so the built-in cert-manager +# approver auto-approves the CertificateRequest and writes the TLS secret. +# The CSI driver's CertificateRequests are not approved by the built-in +# approver, causing pods to hang in Init:0/1. +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: ipam-tls + namespace: ipam-system +spec: + secretName: ipam-tls + duration: 24h + renewBefore: 1h + dnsNames: + - ipam-apiserver + - ipam-apiserver.ipam-system + - ipam-apiserver.ipam-system.svc + - ipam-apiserver.ipam-system.svc.cluster.local + issuerRef: + name: selfsigned-cluster-issuer + kind: ClusterIssuer diff --git a/internal/allocator/interface.go b/internal/allocator/interface.go index 8372b46..5197088 100644 --- a/internal/allocator/interface.go +++ b/internal/allocator/interface.go @@ -38,10 +38,6 @@ type PrefixAllocator interface { // pool identified by poolKey and returns its CIDR string. AllocatePrefix(ctx context.Context, tx pgx.Tx, poolKey string, prefixLen int, ipFamily string, claimKey string, ownerProject string) (string, error) - // AllocateSingleAddress reserves a single host address within the pool - // identified by poolKey and returns its IP string (without prefix). - AllocateSingleAddress(ctx context.Context, tx pgx.Tx, poolKey string, ipFamily string, claimKey string, ownerProject string) (string, error) - // InsertObject writes a generic API object row into ipam_objects inside // the supplied transaction and returns the assigned resource_version. // Callers use the returned rv to populate metadata.resourceVersion on diff --git a/internal/allocator/prefix.go b/internal/allocator/prefix.go index 18d720d..2eb6b70 100644 --- a/internal/allocator/prefix.go +++ b/internal/allocator/prefix.go @@ -85,58 +85,15 @@ func (a *PostgresPrefixAllocator) AllocatePrefix(ctx context.Context, tx pgx.Tx, // set, so the post-allocation utilization can be computed from data // already in scope without an extra DB round-trip. updated := append(append([]net.IPNet(nil), existing...), *cidr) + if err := persistPoolCapacity(ctx, tx, pool, poolKey, parents, updated); err != nil { + return "", fmt.Errorf("update pool capacity after allocation: %w", err) + } publishPrefixUtilization(poolKey, ipFamily, parents, updated) klog.V(2).InfoS("Allocated prefix", "pool", poolKey, "cidr", cidr.String(), "claim", claimKey, "ownerProject", ownerProject) return cidr.String(), nil } -// AllocateSingleAddress implements PrefixAllocator.AllocateSingleAddress. -func (a *PostgresPrefixAllocator) AllocateSingleAddress(ctx context.Context, tx pgx.Tx, poolKey string, ipFamily string, claimKey string, ownerProject string) (string, error) { - pool, err := lockAndDecodePool(ctx, tx, poolKey) - if err != nil { - return "", err - } - - parents, err := parsePoolCIDR(pool) - if err != nil { - return "", err - } - - existing, err := loadExistingAllocations(ctx, tx, poolKey) - if err != nil { - return "", err - } - - hostBits := 32 - if ipFamily == "IPv6" { - hostBits = 128 - } - - strategy := allocation.Strategy(pool.Spec.Allocation.Strategy) - if strategy == "" { - strategy = allocation.FirstFit - } - - cidr, err := allocation.FindFirstAvailableBlock(parents, existing, hostBits, strategy) - if err != nil { - if errors.Is(err, allocation.ErrPoolExhausted) { - return "", ErrPoolExhausted - } - return "", fmt.Errorf("compute next address: %w", err) - } - - if err := insertPrefixAllocation(ctx, tx, poolKey, cidr.String(), claimKey, ipFamily, false, ownerProject); err != nil { - return "", err - } - - updated := append(append([]net.IPNet(nil), existing...), *cidr) - publishPrefixUtilization(poolKey, ipFamily, parents, updated) - - klog.V(2).InfoS("Allocated single address", "pool", poolKey, "addr", cidr.IP.String(), "claim", claimKey, "ownerProject", ownerProject) - return cidr.IP.String(), nil -} - // InsertObject implements PrefixAllocator.InsertObject. func (a *PostgresPrefixAllocator) InsertObject(ctx context.Context, tx pgx.Tx, key, kind, namespace, name string, data []byte) (int64, error) { return insertObject(ctx, tx, key, kind, namespace, name, data) @@ -244,11 +201,45 @@ func (a *PostgresPrefixAllocator) Release(ctx context.Context, tx pgx.Tx, claimK if perr != nil { return fmt.Errorf("reload allocations after release: %w", perr) } + if perr := persistPoolCapacity(ctx, tx, pool, r.poolKey, parents, remaining); perr != nil { + return fmt.Errorf("update pool capacity after release: %w", perr) + } publishPrefixUtilization(r.poolKey, r.ipFamily, parents, remaining) } return nil } +// persistPoolCapacity recomputes Total/Allocated/Available for the pool and +// writes the updated pool object back to ipam_objects (+ MODIFIED changelog) +// within the current transaction. Must be called inside the transaction that +// inserted or deleted the allocation row so the capacity stays consistent. +func persistPoolCapacity(ctx context.Context, tx pgx.Tx, pool *ipamv1alpha1.IPPrefix, poolKey string, parents, allocations []net.IPNet) error { + var total, allocated int64 + for _, p := range parents { + total += allocation.CountAddresses(p) + } + for _, a := range allocations { + allocated += allocation.CountAddresses(a) + } + available := total - allocated + if available < 0 { + available = 0 + } + pool.Status.Capacity = ipamv1alpha1.PrefixCapacity{ + Total: total, + Allocated: allocated, + Available: available, + } + data, err := json.Marshal(pool) + if err != nil { + return fmt.Errorf("marshal pool: %w", err) + } + if _, err := updateObject(ctx, tx, poolKey, data); err != nil { + return fmt.Errorf("write pool: %w", err) + } + return nil +} + // DeleteObject implements PrefixAllocator.DeleteObject. func (a *PostgresPrefixAllocator) DeleteObject(ctx context.Context, tx pgx.Tx, key string) (int64, error) { return deleteObject(ctx, tx, key) diff --git a/internal/apiserver/apiserver.go b/internal/apiserver/apiserver.go index 3141d06..80ef78a 100644 --- a/internal/apiserver/apiserver.go +++ b/internal/apiserver/apiserver.go @@ -23,8 +23,6 @@ import ( _ "go.miloapis.com/ipam/internal/metrics" "go.miloapis.com/ipam/internal/access" "go.miloapis.com/ipam/internal/allocator" - "go.miloapis.com/ipam/internal/registry/ipam/ipaddress" - "go.miloapis.com/ipam/internal/registry/ipam/ipaddressclaim" "go.miloapis.com/ipam/internal/registry/ipam/ipprefix" "go.miloapis.com/ipam/internal/registry/ipam/ipprefixclaim" "go.miloapis.com/ipam/pkg/apis/ipam/install" @@ -181,32 +179,6 @@ func (c completedConfig) New() (*IPAMServer, error) { v1alpha1Storage["ipprefixclaims"] = prefixClaimStore v1alpha1Storage["ipprefixclaims/status"] = prefixClaimStatusStore - // IPAddress — namespaced, with status subresource. - addrStore, addrStatusStore, err := ipaddress.NewStorage(Scheme, c.GenericConfig.RESTOptionsGetter) - if err != nil { - return nil, fmt.Errorf("create IPAddress storage: %w", err) - } - v1alpha1Storage["ipaddresses"] = addrStore - v1alpha1Storage["ipaddresses/status"] = addrStatusStore - - // IPAddressClaim — namespaced, with status subresource. poolChecker - // is passed so cross-project allocation (prefixSelector.projectRef - // targeting another project) goes through the same SAR + visibility - // gate as IPPrefixClaim. - addrClaimStore, addrClaimStatusStore, err := ipaddressclaim.NewAllocatingStorage( - Scheme, - c.GenericConfig.RESTOptionsGetter, - c.ExtraConfig.PrefixAllocator, - c.ExtraConfig.AllocatorPool, - allocCodec, - c.ExtraConfig.PoolChecker, - ) - if err != nil { - return nil, fmt.Errorf("create IPAddressClaim storage: %w", err) - } - v1alpha1Storage["ipaddressclaims"] = addrClaimStore - v1alpha1Storage["ipaddressclaims/status"] = addrClaimStatusStore - apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1Storage if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { diff --git a/internal/registry/ipam/fieldindexes.go b/internal/registry/ipam/fieldindexes.go index aaf8418..0365f57 100644 --- a/internal/registry/ipam/fieldindexes.go +++ b/internal/registry/ipam/fieldindexes.go @@ -2,8 +2,6 @@ package ipamregistry import ( "go.miloapis.com/ipam/internal/fieldindex" - "go.miloapis.com/ipam/internal/registry/ipam/ipaddress" - "go.miloapis.com/ipam/internal/registry/ipam/ipaddressclaim" "go.miloapis.com/ipam/internal/registry/ipam/ipprefix" "go.miloapis.com/ipam/internal/registry/ipam/ipprefixclaim" ) @@ -13,8 +11,6 @@ import ( func AllFieldIndexes() []fieldindex.FieldIndex { var all []fieldindex.FieldIndex all = append(all, ipprefixclaim.FieldIndexes...) - all = append(all, ipaddressclaim.FieldIndexes...) - all = append(all, ipaddress.FieldIndexes...) all = append(all, ipprefix.FieldIndexes...) return all } diff --git a/internal/registry/ipam/ipaddress/storage.go b/internal/registry/ipam/ipaddress/storage.go deleted file mode 100644 index 0ce8887..0000000 --- a/internal/registry/ipam/ipaddress/storage.go +++ /dev/null @@ -1,75 +0,0 @@ -// Package ipaddress provides REST storage for the IPAddress resource. The -// storage is the standard genericregistry.Store backed by the postgres -// RESTOptionsGetter; allocator integration lives in the ipaddressclaim -// package because IPAddress objects are materialised by the IPAddressClaim -// allocating REST or created directly by an operator for fixed assignments. -package ipaddress - -import ( - "context" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/registry/generic" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/apiserver/pkg/registry/rest" - "sigs.k8s.io/structured-merge-diff/v6/fieldpath" - - "go.miloapis.com/ipam/pkg/apis/ipam" - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" -) - -type IPAddressStorage struct { - *genericregistry.Store -} - -type IPAddressStatusStorage struct { - store *genericregistry.Store -} - -func (s *IPAddressStatusStorage) New() runtime.Object { return &ipam.IPAddress{} } -func (s *IPAddressStatusStorage) Destroy() {} - -func (s *IPAddressStatusStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - return s.store.Get(ctx, name, options) -} - -func (s *IPAddressStatusStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, _ bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return s.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) -} - -func (s *IPAddressStatusStorage) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { - return s.store.GetResetFields() -} - -func (s *IPAddressStatusStorage) ConvertToTable(ctx context.Context, obj runtime.Object, opts runtime.Object) (*metav1.Table, error) { - return s.store.ConvertToTable(ctx, obj, opts) -} - -func NewStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*IPAddressStorage, *IPAddressStatusStorage, error) { - strategy := NewStrategy(scheme) - statusStrategy := NewStatusStrategy(scheme) - - store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &ipam.IPAddress{} }, - NewListFunc: func() runtime.Object { return &ipam.IPAddressList{} }, - DefaultQualifiedResource: v1alpha1.Resource("ipaddresses"), - SingularQualifiedResource: v1alpha1.Resource("ipaddress"), - - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, - - TableConvertor: rest.NewDefaultTableConvertor(v1alpha1.Resource("ipaddresses")), - } - - if err := store.CompleteWithOptions(&generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs}); err != nil { - return nil, nil, err - } - - statusStore := *store - statusStore.UpdateStrategy = statusStrategy - statusStore.ResetFieldsStrategy = statusStrategy - - return &IPAddressStorage{store}, &IPAddressStatusStorage{store: &statusStore}, nil -} diff --git a/internal/registry/ipam/ipaddress/strategy.go b/internal/registry/ipam/ipaddress/strategy.go deleted file mode 100644 index fd3e0f3..0000000 --- a/internal/registry/ipam/ipaddress/strategy.go +++ /dev/null @@ -1,183 +0,0 @@ -package ipaddress - -import ( - "context" - "fmt" - "net" - - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/storage" - "k8s.io/apiserver/pkg/storage/names" - "sigs.k8s.io/structured-merge-diff/v6/fieldpath" - - "go.miloapis.com/ipam/internal/fieldindex" - "go.miloapis.com/ipam/pkg/apis/ipam" -) - -// FieldIndexes are the SQL expression indexes that back IPAddress field -// selectors declared in SelectableFields. Applied idempotently by SyncIndexes. -var FieldIndexes = []fieldindex.FieldIndex{ - { - IndexName: "idx_ipam_ipaddress_address", - Expression: `((ipam_data_to_jsonb(data) -> 'spec' ->> 'address')) WHERE kind = 'IPAddress'`, - }, - { - IndexName: "idx_ipam_ipaddress_ip_family", - Expression: `((ipam_data_to_jsonb(data) -> 'spec' ->> 'ipFamily')) WHERE kind = 'IPAddress'`, - }, - { - IndexName: "idx_ipam_ipaddress_prefix_ref_name", - Expression: `((ipam_data_to_jsonb(data) -> 'spec' -> 'prefixRef' ->> 'name')) WHERE kind = 'IPAddress'`, - }, -} - -type ipAddressStrategy struct { - runtime.ObjectTyper - names.NameGenerator -} - -type ipAddressStatusStrategy struct { - runtime.ObjectTyper - names.NameGenerator -} - -func NewStrategy(typer runtime.ObjectTyper) ipAddressStrategy { - return ipAddressStrategy{ObjectTyper: typer, NameGenerator: names.SimpleNameGenerator} -} - -func NewStatusStrategy(typer runtime.ObjectTyper) ipAddressStatusStrategy { - return ipAddressStatusStrategy{ObjectTyper: typer, NameGenerator: names.SimpleNameGenerator} -} - -func (ipAddressStrategy) NamespaceScoped() bool { return true } - -func (ipAddressStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) { - a := obj.(*ipam.IPAddress) - a.Status = ipam.IPAddressStatus{} -} - -func (ipAddressStrategy) PrepareForUpdate(_ context.Context, obj, old runtime.Object) { - n := obj.(*ipam.IPAddress) - o := old.(*ipam.IPAddress) - n.Status = o.Status -} - -func (ipAddressStrategy) Validate(_ context.Context, obj runtime.Object) field.ErrorList { - return validateIPAddress(obj.(*ipam.IPAddress)) -} - -func (ipAddressStrategy) WarningsOnCreate(_ context.Context, _ runtime.Object) []string { return nil } -func (ipAddressStrategy) AllowCreateOnUpdate() bool { return false } -func (ipAddressStrategy) AllowUnconditionalUpdate() bool { return true } -func (ipAddressStrategy) Canonicalize(_ runtime.Object) {} - -func (ipAddressStrategy) ValidateUpdate(_ context.Context, obj, old runtime.Object) field.ErrorList { - n := obj.(*ipam.IPAddress) - o := old.(*ipam.IPAddress) - allErrs := validateIPAddress(n) - if n.Spec.Address != o.Spec.Address { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "address"), "spec.address is immutable")) - } - if n.Spec.IPFamily != o.Spec.IPFamily { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ipFamily"), "spec.ipFamily is immutable")) - } - if n.Spec.PrefixRef != o.Spec.PrefixRef { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "prefixRef"), "spec.prefixRef is immutable")) - } - return allErrs -} - -func (ipAddressStrategy) WarningsOnUpdate(_ context.Context, _, _ runtime.Object) []string { - return nil -} - -func validateIPAddress(a *ipam.IPAddress) field.ErrorList { - var allErrs field.ErrorList - specPath := field.NewPath("spec") - - var parsed net.IP - if a.Spec.Address == "" { - allErrs = append(allErrs, field.Required(specPath.Child("address"), "address is required")) - } else { - parsed = net.ParseIP(a.Spec.Address) - if parsed == nil { - allErrs = append(allErrs, field.Invalid(specPath.Child("address"), a.Spec.Address, "invalid IP address")) - } - } - if a.Spec.IPFamily != ipam.IPv4 && a.Spec.IPFamily != ipam.IPv6 { - allErrs = append(allErrs, field.NotSupported(specPath.Child("ipFamily"), a.Spec.IPFamily, []string{string(ipam.IPv4), string(ipam.IPv6)})) - } - // Cross-check: an IPv4 address must be claimed as IPFamily=IPv4 and - // an IPv6-only address as IPFamily=IPv6. Without this check the - // allocator and consumers downstream would index by ipFamily and - // silently miss the address. net.ParseIP returns a 16-byte slice - // even for IPv4 addresses, so use To4() to discriminate. - if parsed != nil && a.Spec.IPFamily != "" { - isV4 := parsed.To4() != nil - switch { - case isV4 && a.Spec.IPFamily != ipam.IPv4: - allErrs = append(allErrs, field.Invalid(specPath.Child("ipFamily"), a.Spec.IPFamily, - fmt.Sprintf("address %q is IPv4 but ipFamily is %s", a.Spec.Address, a.Spec.IPFamily))) - case !isV4 && a.Spec.IPFamily != ipam.IPv6: - allErrs = append(allErrs, field.Invalid(specPath.Child("ipFamily"), a.Spec.IPFamily, - fmt.Sprintf("address %q is IPv6 but ipFamily is %s", a.Spec.Address, a.Spec.IPFamily))) - } - } - if a.Spec.PrefixRef.Name == "" { - allErrs = append(allErrs, field.Required(specPath.Child("prefixRef", "name"), "prefixRef.name is required")) - } - return allErrs -} - -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { - a, ok := obj.(*ipam.IPAddress) - if !ok { - return nil, nil, fmt.Errorf("given object is not an IPAddress") - } - return a.Labels, SelectableFields(a), nil -} - -func SelectableFields(a *ipam.IPAddress) fields.Set { - objectMetaFields := generic.ObjectMetaFieldsSet(&a.ObjectMeta, true) - return generic.MergeFieldsSets(objectMetaFields, fields.Set{ - "spec.address": a.Spec.Address, - "spec.ipFamily": string(a.Spec.IPFamily), - "spec.prefixRef.name": a.Spec.PrefixRef.Name, - }) -} - -func MatchIPAddress(label labels.Selector, fld fields.Selector) storage.SelectionPredicate { - return storage.SelectionPredicate{Label: label, Field: fld, GetAttrs: GetAttrs} -} - -func (ipAddressStatusStrategy) NamespaceScoped() bool { return true } - -func (ipAddressStatusStrategy) PrepareForUpdate(_ context.Context, obj, old runtime.Object) { - n := obj.(*ipam.IPAddress) - o := old.(*ipam.IPAddress) - n.Spec = o.Spec - n.Labels = o.Labels - n.Annotations = o.Annotations -} - -func (ipAddressStatusStrategy) ValidateUpdate(_ context.Context, _, _ runtime.Object) field.ErrorList { - return nil -} - -func (ipAddressStatusStrategy) WarningsOnUpdate(_ context.Context, _, _ runtime.Object) []string { - return nil -} - -func (ipAddressStatusStrategy) AllowCreateOnUpdate() bool { return false } -func (ipAddressStatusStrategy) AllowUnconditionalUpdate() bool { return true } -func (ipAddressStatusStrategy) Canonicalize(_ runtime.Object) {} - -func (ipAddressStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { - return map[fieldpath.APIVersion]*fieldpath.Set{ - "ipam.miloapis.com/v1alpha1": fieldpath.NewSet(fieldpath.MakePathOrDie("spec")), - } -} diff --git a/internal/registry/ipam/ipaddressclaim/storage.go b/internal/registry/ipam/ipaddressclaim/storage.go deleted file mode 100644 index 1d0a5ac..0000000 --- a/internal/registry/ipam/ipaddressclaim/storage.go +++ /dev/null @@ -1,417 +0,0 @@ -// Package ipaddressclaim provides REST storage for the IPAddressClaim -// resource. The exported AllocatingREST type wraps the standard storage -// with a synchronous Postgres-backed allocator that reserves a single -// host IP address from the parent IPPrefix pool. -package ipaddressclaim - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/jackc/pgx/v5/pgxpool" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/registry/generic" - genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" - "k8s.io/apiserver/pkg/registry/rest" - "k8s.io/apiserver/pkg/storage" - "k8s.io/klog/v2" - "sigs.k8s.io/structured-merge-diff/v6/fieldpath" - - "go.miloapis.com/ipam/internal/access" - "go.miloapis.com/ipam/internal/allocator" - "go.miloapis.com/ipam/internal/metrics" - "go.miloapis.com/ipam/internal/registry/ipam/registryerrors" - "go.miloapis.com/ipam/internal/tenant" - "go.miloapis.com/ipam/pkg/apis/ipam" - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" -) - -type IPAddressClaimStorage struct { - *genericregistry.Store -} - -type IPAddressClaimStatusStorage struct { - store *genericregistry.Store -} - -func (s *IPAddressClaimStatusStorage) New() runtime.Object { return &ipam.IPAddressClaim{} } -func (s *IPAddressClaimStatusStorage) Destroy() {} - -func (s *IPAddressClaimStatusStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - return s.store.Get(ctx, name, options) -} - -func (s *IPAddressClaimStatusStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, _ bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return s.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) -} - -func (s *IPAddressClaimStatusStorage) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { - return s.store.GetResetFields() -} - -func (s *IPAddressClaimStatusStorage) ConvertToTable(ctx context.Context, obj runtime.Object, opts runtime.Object) (*metav1.Table, error) { - return s.store.ConvertToTable(ctx, obj, opts) -} - -func newInnerStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*IPAddressClaimStorage, *IPAddressClaimStatusStorage, error) { - strategy := NewStrategy(scheme) - statusStrategy := NewStatusStrategy(scheme) - - store := &genericregistry.Store{ - NewFunc: func() runtime.Object { return &ipam.IPAddressClaim{} }, - NewListFunc: func() runtime.Object { return &ipam.IPAddressClaimList{} }, - DefaultQualifiedResource: v1alpha1.Resource("ipaddressclaims"), - SingularQualifiedResource: v1alpha1.Resource("ipaddressclaim"), - - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, - - TableConvertor: rest.NewDefaultTableConvertor(v1alpha1.Resource("ipaddressclaims")), - } - - if err := store.CompleteWithOptions(&generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs}); err != nil { - return nil, nil, err - } - - statusStore := *store - statusStore.UpdateStrategy = statusStrategy - statusStore.ResetFieldsStrategy = statusStrategy - - return &IPAddressClaimStorage{store}, &IPAddressClaimStatusStorage{store: &statusStore}, nil -} - -type AllocatingREST struct { - *IPAddressClaimStorage - allocator allocator.PrefixAllocator - db *pgxpool.Pool - strategy ipAddressClaimStrategy - poolChecker access.PoolAccessChecker - codec runtime.Codec -} - -// NewAllocatingStorage builds the IPAddressClaim REST storage with -// synchronous Postgres-backed allocation. poolChecker may be nil; when -// non-nil it authorises cross-project claims (prefixSelector.projectRef -// targeting another project) via SubjectAccessReview before allocation. -// When nil, cross-project allocation fails closed — the visibility=shared -// marker on the IPPrefixClass is intent-only and never sufficient on its -// own. Mirrors the IPPrefixClaim auth pattern (audit findings H1/H6, -// task #20). -func NewAllocatingStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter, alloc allocator.PrefixAllocator, db *pgxpool.Pool, codec runtime.Codec, poolChecker access.PoolAccessChecker) (*AllocatingREST, *IPAddressClaimStatusStorage, error) { - claimStore, statusStore, err := newInnerStorage(scheme, optsGetter) - if err != nil { - return nil, nil, err - } - return &AllocatingREST{ - IPAddressClaimStorage: claimStore, - allocator: alloc, - db: db, - strategy: NewStrategy(scheme), - poolChecker: poolChecker, - codec: codec, - }, statusStore, nil -} - -func (r *AllocatingREST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { - claim, ok := obj.(*ipam.IPAddressClaim) - if !ok { - return nil, fmt.Errorf("expected *ipam.IPAddressClaim, got %T", obj) - } - // Extract tenant identity up front so the project / org labels are - // available to AllocationAttempts and the deferred AllocationDuration - // observation. project / org come from tenant.Identity helpers - // (iam.miloapis.com/parent-* extras); both are "" for platform-scoped - // requests, and org is "" today for project-scoped requests until Milo - // forwards the owning org alongside the project. - id := tenant.FromContext(ctx) - project := id.Project() - org := id.Org() - // ip_family is sourced from claim.Spec.IPFamily before any metric is - // recorded so AllocationAttempts, AllocationFailures, and the latency - // histogram all split identically. claim.Spec.IPFamily is set on every - // valid IPAddressClaim ("IPv4" or "IPv6"); pre-spec failures land in the - // empty-string family and are clearly distinguishable from the - // family-tagged successes. - ipFamily := string(claim.Spec.IPFamily) - metrics.AllocationAttempts.WithLabelValues("ipaddressclaim", ipFamily, project, org).Inc() - allocStart := time.Now() - result := "error" - defer func() { - metrics.ObserveAllocationDuration("ipaddressclaim", result, ipFamily, project, org, allocStart) - }() - - objectMeta, err := meta.Accessor(claim) - if err != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, fmt.Errorf("get object metadata: %w", err) - } - rest.FillObjectMetaSystemFields(objectMeta) - - if err := rest.BeforeCreate(r.strategy, ctx, claim); err != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, err - } - if createValidation != nil { - if err := createValidation(ctx, claim.DeepCopyObject()); err != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, err - } - } - - if claim.Spec.PrefixRef == nil && claim.Spec.PrefixSelector == nil { - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, apierrors.NewBadRequest("synchronous allocation requires spec.prefixRef or spec.prefixSelector") - } - if claim.Spec.PrefixRef != nil && claim.Spec.PrefixSelector != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, apierrors.NewBadRequest("spec.prefixRef and spec.prefixSelector are mutually exclusive") - } - - if !id.IsPlatform() { - claim.Spec.OwnerRef = &ipam.ObjectRef{ - APIGroup: id.APIGroup, - Kind: id.Kind, - Name: id.Name, - } - } - - tx, err := r.db.Begin(ctx) - if err != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "tx_error", ipFamily, project, org) - return nil, fmt.Errorf("begin allocation transaction: %w", err) - } - - // Resolve the target prefix pool. spec.prefixRef is a direct named - // lookup; spec.prefixSelector lists candidates and picks the first - // match (allocator.ResolvePrefixPool documents the strategy). Both - // paths support an optional cross-project ProjectRef pointing at a - // foreign project's pool; that branch sets isCrossProject so we can - // run the same SAR + visibility=shared gate as IPPrefixClaim before - // allocating (audit findings H1/H6 — task #20). - isCrossProject := false - var poolKey string - if claim.Spec.PrefixRef != nil { - isCrossProject = !id.IsPlatform() && - claim.Spec.PrefixRef.ProjectRef != nil && - claim.Spec.PrefixRef.ProjectRef.Name != id.Name - if isCrossProject { - poolKey = tenant.Identity{Name: claim.Spec.PrefixRef.ProjectRef.Name}.ResourceKey("ipprefixes", claim.Spec.PrefixRef.Name) - } else { - poolKey = id.ResourceKey("ipprefixes", claim.Spec.PrefixRef.Name) - } - } else { - ownerProject := id.Name - if claim.Spec.PrefixSelector.ProjectRef != nil { - ownerProject = claim.Spec.PrefixSelector.ProjectRef.Name - isCrossProject = !id.IsPlatform() && ownerProject != id.Name - } - resolved, rerr := allocator.ResolvePrefixPool(ctx, tx, claim.Spec.PrefixSelector.LabelSelector, ownerProject, string(claim.Spec.IPFamily)) - if rerr != nil { - _ = tx.Rollback(ctx) - if errors.Is(rerr, allocator.ErrPoolNotFound) { - metrics.RecordAllocationFailure("ipaddressclaim", "pool_not_found", ipFamily, project, org) - return nil, apierrors.NewBadRequest("no IPPrefix pool matches spec.prefixSelector") - } - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, fmt.Errorf("resolve prefix pool: %w", rerr) - } - poolKey = resolved - } - claimKey := claimObjectKey(claim.Namespace, claim.Name) - - if isCrossProject { - if err := access.AuthorizeCrossProjectPrefix(ctx, tx, poolKey, r.poolChecker); err != nil { - _ = tx.Rollback(ctx) - if errors.Is(err, access.ErrCrossProjectDenied) { - // Mask the failure so the selector path can't be used to - // fingerprint another project's pools by trial labels — - // the response must be indistinguishable from "no pool - // matched the selector". Direct prefixRef lookups can - // return Forbidden because the caller already named the - // pool by hand, so revealing forbidden-vs-not-found - // reveals nothing new. - if claim.Spec.PrefixSelector != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "pool_not_found", ipFamily, project, org) - return nil, apierrors.NewBadRequest("no IPPrefix pool matches spec.prefixSelector") - } - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, apierrors.NewForbidden( - v1alpha1.Resource("ipprefixes"), - poolKey, - fmt.Errorf("cross-project pool not accessible"), - ) - } - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, err - } - } - - addr, err := r.allocator.AllocateSingleAddress(ctx, tx, poolKey, string(claim.Spec.IPFamily), claimKey, id.Name) - if err != nil { - _ = tx.Rollback(ctx) - reason := allocationFailureReason(err) - metrics.RecordAllocationFailure("ipaddressclaim", reason, ipFamily, project, org) - if reason == "pool_exhausted" { - result = "exhausted" - } - return nil, mapAllocationError(err) - } - - claim.Status.Phase = ipam.ClaimBound - claim.Status.AllocatedIP = addr - - claimData, err := runtime.Encode(r.codec, claim) - if err != nil { - _ = tx.Rollback(ctx) - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, fmt.Errorf("encode claim: %w", err) - } - rv, err := r.allocator.InsertObject(ctx, tx, claimKey, "IPAddressClaim", claim.Namespace, claim.Name, claimData) - if err != nil { - _ = tx.Rollback(ctx) - metrics.RecordAllocationFailure("ipaddressclaim", "tx_error", ipFamily, project, org) - return nil, fmt.Errorf("persist claim: %w", err) - } - versioner := storage.APIObjectVersioner{} - if err := versioner.UpdateObject(claim, uint64(rv)); err != nil { - _ = tx.Rollback(ctx) - metrics.RecordAllocationFailure("ipaddressclaim", "internal", ipFamily, project, org) - return nil, fmt.Errorf("set resource version: %w", err) - } - - if err := tx.Commit(ctx); err != nil { - metrics.RecordAllocationFailure("ipaddressclaim", "tx_error", ipFamily, project, org) - return nil, fmt.Errorf("commit allocation transaction: %w", err) - } - result = "success" - return claim, nil -} - -// allocationFailureReason maps an allocator error onto the canonical reason -// label used by ipam_allocation_failures_total. -func allocationFailureReason(err error) string { - switch { - case errors.Is(err, allocator.ErrPoolExhausted): - return "pool_exhausted" - case errors.Is(err, allocator.ErrPoolNotFound): - return "pool_not_found" - default: - return "tx_error" - } -} - -// Delete runs the claim teardown in two transactions so watchers can observe -// the intermediate phase=Releasing state before the object disappears. See -// the IPPrefixClaim Delete handler for the full rationale; this is the same -// pattern adapted to IPAddressClaim. -func (r *AllocatingREST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - existing, err := r.Get(ctx, name, &metav1.GetOptions{}) - if err != nil { - return nil, false, err - } - claim, ok := existing.(*ipam.IPAddressClaim) - if !ok { - return nil, false, fmt.Errorf("expected *ipam.IPAddressClaim from Get, got %T", existing) - } - if deleteValidation != nil { - if err := deleteValidation(ctx, claim.DeepCopyObject()); err != nil { - return nil, false, err - } - } - claimKey := claimObjectKey(claim.Namespace, claim.Name) - - // TX1 — publish phase=Releasing. - releasing := claim.DeepCopy() - releasing.Status.Phase = ipam.ClaimReleasing - releasingData, err := runtime.Encode(r.codec, releasing) - if err != nil { - return nil, false, fmt.Errorf("encode releasing claim: %w", err) - } - tx1, err := r.db.Begin(ctx) - if err != nil { - return nil, false, fmt.Errorf("begin releasing transaction: %w", err) - } - rv, err := r.allocator.UpdateObject(ctx, tx1, claimKey, releasingData) - if err != nil { - _ = tx1.Rollback(ctx) - return nil, false, fmt.Errorf("publish releasing phase: %w", err) - } - versioner := storage.APIObjectVersioner{} - if err := versioner.UpdateObject(releasing, uint64(rv)); err != nil { - _ = tx1.Rollback(ctx) - return nil, false, fmt.Errorf("set releasing resource version: %w", err) - } - if err := tx1.Commit(ctx); err != nil { - return nil, false, fmt.Errorf("commit releasing transaction: %w", err) - } - klog.V(2).InfoS("claim entering Releasing phase", "claim", name) - - // TX2 — release the allocation and delete the object row, with retry. - var lastErr error - for attempt := 1; attempt <= deleteMaxAttempts; attempt++ { - lastErr = r.releaseAndDelete(ctx, claimKey) - if lastErr == nil { - break - } - klog.ErrorS(lastErr, "release-and-delete attempt failed", "claim", name, "attempt", attempt) - if attempt < deleteMaxAttempts { - time.Sleep(deleteRetryBackoff) - } - } - if lastErr != nil { - klog.ErrorS(lastErr, "claim stuck in Releasing after retries — manual intervention may be required", "claim", name, "attempts", deleteMaxAttempts) - return nil, false, fmt.Errorf("release allocation after %d attempts: %w", deleteMaxAttempts, lastErr) - } - - klog.V(2).InfoS("claim released and deleted", "claim", name) - metrics.RecordRelease("ipaddressclaim") - return releasing, true, nil -} - -// releaseAndDelete is a single attempt of TX2: release the allocation row(s) -// for claimKey and delete the object row, all inside one transaction. -func (r *AllocatingREST) releaseAndDelete(ctx context.Context, claimKey string) error { - tx, err := r.db.Begin(ctx) - if err != nil { - return fmt.Errorf("begin release transaction: %w", err) - } - if err := r.allocator.Release(ctx, tx, claimKey); err != nil { - _ = tx.Rollback(ctx) - return fmt.Errorf("release allocation: %w", err) - } - if _, err := r.allocator.DeleteObject(ctx, tx, claimKey); err != nil { - _ = tx.Rollback(ctx) - return fmt.Errorf("delete claim row: %w", err) - } - if err := tx.Commit(ctx); err != nil { - return fmt.Errorf("commit release transaction: %w", err) - } - return nil -} - -// deleteMaxAttempts and deleteRetryBackoff govern the TX2 retry loop. -const ( - deleteMaxAttempts = 3 - deleteRetryBackoff = 100 * time.Millisecond -) - -func claimObjectKey(namespace, name string) string { - return fmt.Sprintf("/ipam.miloapis.com/ipaddressclaims/%s/%s", namespace, name) -} - -func mapAllocationError(err error) error { - switch { - case errors.Is(err, allocator.ErrPoolExhausted): - return registryerrors.NewInsufficientStorage("address pool exhausted") - case errors.Is(err, allocator.ErrPoolNotFound): - return apierrors.NewBadRequest("address pool not found") - default: - return apierrors.NewInternalError(err) - } -} diff --git a/internal/registry/ipam/ipaddressclaim/strategy.go b/internal/registry/ipam/ipaddressclaim/strategy.go deleted file mode 100644 index 24daf8b..0000000 --- a/internal/registry/ipam/ipaddressclaim/strategy.go +++ /dev/null @@ -1,167 +0,0 @@ -package ipaddressclaim - -import ( - "context" - "fmt" - - "k8s.io/apimachinery/pkg/api/equality" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/storage" - "k8s.io/apiserver/pkg/storage/names" - "sigs.k8s.io/structured-merge-diff/v6/fieldpath" - - "go.miloapis.com/ipam/internal/fieldindex" - "go.miloapis.com/ipam/pkg/apis/ipam" -) - -// FieldIndexes are the SQL expression indexes that back IPAddressClaim field -// selectors declared in SelectableFields. Applied idempotently by SyncIndexes. -var FieldIndexes = []fieldindex.FieldIndex{ - { - IndexName: "idx_ipam_ipaddressclaim_ip_family", - Expression: `((ipam_data_to_jsonb(data) -> 'spec' ->> 'ipFamily')) WHERE kind = 'IPAddressClaim'`, - }, - { - IndexName: "idx_ipam_ipaddressclaim_prefix_ref_name", - Expression: `((ipam_data_to_jsonb(data) -> 'spec' -> 'prefixRef' ->> 'name')) WHERE kind = 'IPAddressClaim'`, - }, -} - -type ipAddressClaimStrategy struct { - runtime.ObjectTyper - names.NameGenerator -} - -type ipAddressClaimStatusStrategy struct { - runtime.ObjectTyper - names.NameGenerator -} - -func NewStrategy(typer runtime.ObjectTyper) ipAddressClaimStrategy { - return ipAddressClaimStrategy{ObjectTyper: typer, NameGenerator: names.SimpleNameGenerator} -} - -func NewStatusStrategy(typer runtime.ObjectTyper) ipAddressClaimStatusStrategy { - return ipAddressClaimStatusStrategy{ObjectTyper: typer, NameGenerator: names.SimpleNameGenerator} -} - -func (ipAddressClaimStrategy) NamespaceScoped() bool { return true } - -func (ipAddressClaimStrategy) PrepareForCreate(_ context.Context, obj runtime.Object) { - c := obj.(*ipam.IPAddressClaim) - c.Status = ipam.IPAddressClaimStatus{Phase: ipam.ClaimPending} -} - -func (ipAddressClaimStrategy) PrepareForUpdate(_ context.Context, obj, old runtime.Object) { - n := obj.(*ipam.IPAddressClaim) - o := old.(*ipam.IPAddressClaim) - n.Status = o.Status -} - -func (ipAddressClaimStrategy) Validate(_ context.Context, obj runtime.Object) field.ErrorList { - return validateIPAddressClaim(obj.(*ipam.IPAddressClaim)) -} - -func (ipAddressClaimStrategy) WarningsOnCreate(_ context.Context, _ runtime.Object) []string { - return nil -} - -func (ipAddressClaimStrategy) AllowCreateOnUpdate() bool { return false } -func (ipAddressClaimStrategy) AllowUnconditionalUpdate() bool { return true } -func (ipAddressClaimStrategy) Canonicalize(_ runtime.Object) {} - -func (ipAddressClaimStrategy) ValidateUpdate(_ context.Context, obj, old runtime.Object) field.ErrorList { - n := obj.(*ipam.IPAddressClaim) - o := old.(*ipam.IPAddressClaim) - allErrs := validateIPAddressClaim(n) - if n.Spec.IPFamily != o.Spec.IPFamily { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "ipFamily"), "ipFamily is immutable")) - } - if !equality.Semantic.DeepEqual(n.Spec.PrefixRef, o.Spec.PrefixRef) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "prefixRef"), "prefixRef is immutable")) - } - if !equality.Semantic.DeepEqual(n.Spec.PrefixSelector, o.Spec.PrefixSelector) { - allErrs = append(allErrs, field.Forbidden(field.NewPath("spec", "prefixSelector"), "prefixSelector is immutable")) - } - return allErrs -} - -func (ipAddressClaimStrategy) WarningsOnUpdate(_ context.Context, _, _ runtime.Object) []string { - return nil -} - -func validateIPAddressClaim(c *ipam.IPAddressClaim) field.ErrorList { - var allErrs field.ErrorList - specPath := field.NewPath("spec") - if c.Spec.IPFamily == "" { - allErrs = append(allErrs, field.Required(specPath.Child("ipFamily"), "ipFamily is required")) - } else if c.Spec.IPFamily != ipam.IPv4 && c.Spec.IPFamily != ipam.IPv6 { - allErrs = append(allErrs, field.NotSupported(specPath.Child("ipFamily"), c.Spec.IPFamily, []string{string(ipam.IPv4), string(ipam.IPv6)})) - } - if c.Spec.PrefixRef == nil && c.Spec.PrefixSelector == nil { - allErrs = append(allErrs, field.Required(specPath, "exactly one of prefixRef or prefixSelector must be specified")) - } - if c.Spec.PrefixRef != nil && c.Spec.PrefixSelector != nil { - allErrs = append(allErrs, field.Forbidden(specPath, "prefixRef and prefixSelector are mutually exclusive")) - } - return allErrs -} - -func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { - c, ok := obj.(*ipam.IPAddressClaim) - if !ok { - return nil, nil, fmt.Errorf("given object is not an IPAddressClaim") - } - return c.Labels, SelectableFields(c), nil -} - -func SelectableFields(c *ipam.IPAddressClaim) fields.Set { - objectMetaFields := generic.ObjectMetaFieldsSet(&c.ObjectMeta, true) - // spec.prefixRef.name surfaces the targeted pool for filtered - // watches/lists (e.g. "show all address claims against this pool"). - // Empty for selector-based claims by design. - prefixRefName := "" - if c.Spec.PrefixRef != nil { - prefixRefName = c.Spec.PrefixRef.Name - } - return generic.MergeFieldsSets(objectMetaFields, fields.Set{ - "spec.ipFamily": string(c.Spec.IPFamily), - "spec.prefixRef.name": prefixRefName, - }) -} - -func MatchIPAddressClaim(label labels.Selector, fld fields.Selector) storage.SelectionPredicate { - return storage.SelectionPredicate{Label: label, Field: fld, GetAttrs: GetAttrs} -} - -func (ipAddressClaimStatusStrategy) NamespaceScoped() bool { return true } - -func (ipAddressClaimStatusStrategy) PrepareForUpdate(_ context.Context, obj, old runtime.Object) { - n := obj.(*ipam.IPAddressClaim) - o := old.(*ipam.IPAddressClaim) - n.Spec = o.Spec - n.Labels = o.Labels - n.Annotations = o.Annotations -} - -func (ipAddressClaimStatusStrategy) ValidateUpdate(_ context.Context, _, _ runtime.Object) field.ErrorList { - return nil -} - -func (ipAddressClaimStatusStrategy) WarningsOnUpdate(_ context.Context, _, _ runtime.Object) []string { - return nil -} - -func (ipAddressClaimStatusStrategy) AllowCreateOnUpdate() bool { return false } -func (ipAddressClaimStatusStrategy) AllowUnconditionalUpdate() bool { return true } -func (ipAddressClaimStatusStrategy) Canonicalize(_ runtime.Object) {} - -func (ipAddressClaimStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { - return map[fieldpath.APIVersion]*fieldpath.Set{ - "ipam.miloapis.com/v1alpha1": fieldpath.NewSet(fieldpath.MakePathOrDie("spec")), - } -} diff --git a/internal/registry/ipam/ipprefixclaim/storage.go b/internal/registry/ipam/ipprefixclaim/storage.go index 2b00f06..7fc9f8c 100644 --- a/internal/registry/ipam/ipprefixclaim/storage.go +++ b/internal/registry/ipam/ipprefixclaim/storage.go @@ -17,6 +17,7 @@ import ( "github.com/jackc/pgx/v5/pgxpool" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" @@ -233,21 +234,21 @@ func (r *AllocatingREST) Create(ctx context.Context, obj runtime.Object, createV isCrossProject = !id.IsPlatform() && claim.Spec.PrefixRef.ProjectRef != nil && claim.Spec.PrefixRef.ProjectRef.Name != id.Name - if isCrossProject { - poolKey = tenant.Identity{Name: claim.Spec.PrefixRef.ProjectRef.Name}.ResourceKey("ipprefixes", poolName) - } else { - poolKey = id.ResourceKey("ipprefixes", poolName) - } + // IPPrefix is cluster-scoped; pools are always stored at the platform + // key regardless of the calling project's tenant identity. The tenant + // identity governs ownerRef stamping and cross-project authorization, + // not where the pool row lives in ipam_objects. + poolKey = "/ipam.miloapis.com/ipprefixes/" + poolName } else { - // PrefixSelector path. The selector's optional ProjectRef lets a - // claim target a specific project's pools; absent that, scope to - // the caller's own project (or platform). - ownerProject := id.Name + // PrefixSelector path. IPPrefix pools are cluster-scoped so they are + // always stored at platform keys. Pass ownerProject="" so listPools + // scans the platform prefix; the label selector and ipFamily filter + // narrow the result to the appropriate pool. if claim.Spec.PrefixSelector.ProjectRef != nil { - ownerProject = claim.Spec.PrefixSelector.ProjectRef.Name - isCrossProject = !id.IsPlatform() && ownerProject != id.Name + isCrossProject = !id.IsPlatform() && + claim.Spec.PrefixSelector.ProjectRef.Name != id.Name } - resolved, rerr := allocator.ResolvePrefixPool(ctx, tx, claim.Spec.PrefixSelector.LabelSelector, ownerProject, string(claim.Spec.IPFamily)) + resolved, rerr := allocator.ResolvePrefixPool(ctx, tx, claim.Spec.PrefixSelector.LabelSelector, "", string(claim.Spec.IPFamily)) if rerr != nil { _ = tx.Rollback(ctx) if errors.Is(rerr, allocator.ErrPoolNotFound) { @@ -489,6 +490,42 @@ func (r *AllocatingREST) Delete(ctx context.Context, name string, deleteValidati return releasing, true, nil } +// DeleteCollection overrides the embedded genericregistry.Store.DeleteCollection so that +// each individual claim is deleted through AllocatingREST.Delete rather than the store's +// own Delete. This is necessary because the embedded Store's method set uses Go's static +// dispatch: Store.DeleteCollection calls Store.Delete (not our override), so allocations +// would never be released when the namespace controller sends a bulk DELETE for a +// namespace being terminated. +func (r *AllocatingREST) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) { + listObj, err := r.List(ctx, listOptions) + if err != nil { + return nil, fmt.Errorf("list claims for deletecollection: %w", err) + } + claimList, ok := listObj.(*ipam.IPPrefixClaimList) + if !ok { + return nil, fmt.Errorf("expected *ipam.IPPrefixClaimList from List, got %T", listObj) + } + + deletedList := &ipam.IPPrefixClaimList{} + var errs []error + for i := range claimList.Items { + deleted, _, err := r.Delete(ctx, claimList.Items[i].Name, deleteValidation, options.DeepCopy()) + if err != nil { + if !apierrors.IsNotFound(err) { + errs = append(errs, fmt.Errorf("delete claim %s: %w", claimList.Items[i].Name, err)) + } + continue + } + if c, ok := deleted.(*ipam.IPPrefixClaim); ok { + deletedList.Items = append(deletedList.Items, *c) + } + } + if len(errs) > 0 { + return deletedList, errors.Join(errs...) + } + return deletedList, nil +} + // releaseAndDelete is a single attempt of TX2: release the allocation row(s) // for claimKey and delete the object row, all inside one transaction. func (r *AllocatingREST) releaseAndDelete(ctx context.Context, claimKey string) error { diff --git a/internal/watch/postgres.go b/internal/watch/postgres.go index 8d20390..0eb0e3a 100644 --- a/internal/watch/postgres.go +++ b/internal/watch/postgres.go @@ -553,7 +553,7 @@ func (w *postgresWatch) poll(ctx context.Context) { if rv > uint64(w.lastRV) { w.lastRV = int64(rv) } - w.sendBookmarkAt(uint64(w.lastRV)) + w.sendBookmarkBlocking(uint64(w.lastRV)) case <-w.kick: // LISTEN/NOTIFY push: a Postgres notification told us // there's new data. Wait briefly so any additional kicks @@ -696,14 +696,16 @@ func (w *postgresWatch) sendInitialEventList(ctx context.Context) error { } listArgs := []any{w.key, keyPrefix + "%"} - listQuery := `SELECT key, resource_version, data + var listQueryBuilder strings.Builder + listQueryBuilder.WriteString(`SELECT key, resource_version, data FROM ipam_objects - WHERE (key = $1 OR key LIKE $2)` + WHERE (key = $1 OR key LIKE $2)`) for _, excl := range w.excludedKeyPrefixes { listArgs = append(listArgs, excl+"%") - listQuery += fmt.Sprintf(" AND key NOT LIKE $%d", len(listArgs)) + fmt.Fprintf(&listQueryBuilder, " AND key NOT LIKE $%d", len(listArgs)) } - listQuery += " ORDER BY resource_version ASC" + listQueryBuilder.WriteString(" ORDER BY resource_version ASC") + listQuery := listQueryBuilder.String() rows, err := tx.QueryContext(ctx, listQuery, listArgs...) if err != nil { @@ -890,11 +892,14 @@ func (w *postgresWatch) pollChanges(ctx context.Context) (int, error) { WHERE commit_xid < $1 AND (commit_xid > $2 OR (commit_xid = $2 AND id > $3)) AND (key = $4 OR key LIKE $5)` + var queryBuilder strings.Builder + queryBuilder.WriteString(query) for _, excl := range w.excludedKeyPrefixes { args = append(args, excl+"%") - query += fmt.Sprintf(" AND key NOT LIKE $%d", len(args)) + fmt.Fprintf(&queryBuilder, " AND key NOT LIKE $%d", len(args)) } - query += fmt.Sprintf(" ORDER BY commit_xid ASC, id ASC LIMIT %d", pollBatchSize) + fmt.Fprintf(&queryBuilder, " ORDER BY commit_xid ASC, id ASC LIMIT %d", pollBatchSize) + query = queryBuilder.String() rows, err := w.db.QueryContext(ctx, query, args...) if err != nil { @@ -1069,8 +1074,9 @@ func (w *postgresWatch) sendBookmark() { } // sendBookmarkAt emits a bookmark event with the supplied resource version. -// Used both by the periodic bookmark ticker and by RequestWatchProgress to -// signal "the storage is at least at this RV". +// Used by the periodic 30-second bookmark ticker where dropping an occasional +// bookmark is harmless — if the channel is full the ticker will fire again +// shortly and deliver the next one. func (w *postgresWatch) sendBookmarkAt(rv uint64) { if w.newFunc == nil { return @@ -1089,6 +1095,41 @@ func (w *postgresWatch) sendBookmarkAt(rv uint64) { } case <-w.done: default: - // Channel full — caller will retry + // Channel full — the periodic ticker will deliver the next bookmark soon. + } +} + +// sendBookmarkBlocking emits a bookmark event with the supplied resource +// version, blocking until the event is delivered or the watch is stopped. +// +// This MUST be used on the RequestWatchProgress path (the progress channel +// handler) rather than sendBookmarkAt. The cacher's +// waitUntilWatchCacheFreshAndForceAllEvents blocks for up to 3 seconds +// waiting for a bookmark at the requested RV; if the bookmark is dropped +// because the result channel is momentarily full, the cacher never unblocks +// and the WatchList request (kubectl v1.35+ `--for=condition=Ready`) returns +// TooLargeResourceVersionError, causing kubectl to retry indefinitely. +// +// Blocking here is safe: the ConditionalProgressRequester fires at most every +// 100 ms, the result channel is 100-deep, and the cacher drains it faster than +// any realistic write burst, so the actual wait is sub-millisecond in practice. +func (w *postgresWatch) sendBookmarkBlocking(rv uint64) { + if w.newFunc == nil { + return + } + obj := w.newFunc() + versioner := storage.APIObjectVersioner{} + if err := versioner.UpdateObject(obj, rv); err != nil { + klog.ErrorS(err, "Failed to set resource version on bookmark object") + return + } + event := watch.Event{Type: watch.Bookmark, Object: obj} + select { + case w.result <- event: + if int64(rv) > w.lastRV { + w.lastRV = int64(rv) + } + case <-w.done: + // Watch stopped — nothing to do. } } diff --git a/pkg/apis/ipam/protobuf.go b/pkg/apis/ipam/protobuf.go index 3890379..3ed43aa 100644 --- a/pkg/apis/ipam/protobuf.go +++ b/pkg/apis/ipam/protobuf.go @@ -26,17 +26,4 @@ func (in *IPPrefixClaim) Unmarshal(data []byte) error { return json.Unmarsha func (in *IPPrefixClaimList) Marshal() ([]byte, error) { return json.Marshal(in) } func (in *IPPrefixClaimList) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } -// --- IPAddress --- - -func (in *IPAddress) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddress) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } -func (in *IPAddressList) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddressList) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } - -// --- IPAddressClaim --- - -func (in *IPAddressClaim) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddressClaim) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } -func (in *IPAddressClaimList) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddressClaimList) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } diff --git a/pkg/apis/ipam/register.go b/pkg/apis/ipam/register.go index 9bf97ef..a10a283 100644 --- a/pkg/apis/ipam/register.go +++ b/pkg/apis/ipam/register.go @@ -34,8 +34,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { &IPPrefixClass{}, &IPPrefixClassList{}, &IPPrefix{}, &IPPrefixList{}, &IPPrefixClaim{}, &IPPrefixClaimList{}, - &IPAddress{}, &IPAddressList{}, - &IPAddressClaim{}, &IPAddressClaimList{}, ) return nil } diff --git a/pkg/apis/ipam/types.go b/pkg/apis/ipam/types.go index d3839fa..84abd97 100644 --- a/pkg/apis/ipam/types.go +++ b/pkg/apis/ipam/types.go @@ -116,8 +116,11 @@ type IPPrefixClass struct { } type IPPrefixClassSpec struct { - Visibility string - DefaultAllocation AllocationSpec + // RequiresVerification indicates that IP prefixes borrowing from this + // class must be verified before they can be used (e.g. BYOIP flows). + RequiresVerification bool + Visibility string + DefaultAllocation AllocationSpec } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -206,75 +209,4 @@ type IPPrefixClaimList struct { Items []IPPrefixClaim } -// ---------------------------------------------------------------------------- -// IPAddress -// ---------------------------------------------------------------------------- - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +genclient - -type IPAddress struct { - metav1.TypeMeta - metav1.ObjectMeta - - Spec IPAddressSpec - Status IPAddressStatus -} - -type IPAddressSpec struct { - Address string - IPFamily IPFamily - PrefixRef LocalRef - ClaimRef *LocalRef -} - -type IPAddressStatus struct { - Conditions []metav1.Condition -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type IPAddressList struct { - metav1.TypeMeta - metav1.ListMeta - Items []IPAddress -} - -// ---------------------------------------------------------------------------- -// IPAddressClaim -// ---------------------------------------------------------------------------- - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// +genclient - -type IPAddressClaim struct { - metav1.TypeMeta - metav1.ObjectMeta - - Spec IPAddressClaimSpec - Status IPAddressClaimStatus -} - -type IPAddressClaimSpec struct { - IPFamily IPFamily - PrefixSelector *PrefixSelector - PrefixRef *NamespacedRef - ReclaimPolicy ReclaimPolicy - OwnerRef *ObjectRef -} - -type IPAddressClaimStatus struct { - Phase ClaimPhase - AllocatedIP string - BoundAddressRef *LocalRef - Conditions []metav1.Condition -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type IPAddressClaimList struct { - metav1.TypeMeta - metav1.ListMeta - Items []IPAddressClaim -} diff --git a/pkg/apis/ipam/v1alpha1/conversion.go b/pkg/apis/ipam/v1alpha1/conversion.go index 821cd1a..8d9a42c 100644 --- a/pkg/apis/ipam/v1alpha1/conversion.go +++ b/pkg/apis/ipam/v1alpha1/conversion.go @@ -13,100 +13,64 @@ import ( // named type, so conversion is a series of mechanical field copies. func RegisterConversions(s *runtime.Scheme) error { pairs := []struct { - internal, external interface{} + internal, external any toInternal conversion.ConversionFunc toExternal conversion.ConversionFunc }{ { (*ipam.IPPrefixClass)(nil), (*IPPrefixClass)(nil), - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_v1alpha1_IPPrefixClass_To_ipam(a.(*IPPrefixClass), b.(*ipam.IPPrefixClass)) }, - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_ipam_IPPrefixClass_To_v1alpha1(a.(*ipam.IPPrefixClass), b.(*IPPrefixClass)) }, }, { (*ipam.IPPrefixClassList)(nil), (*IPPrefixClassList)(nil), - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_v1alpha1_IPPrefixClassList_To_ipam(a.(*IPPrefixClassList), b.(*ipam.IPPrefixClassList)) }, - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_ipam_IPPrefixClassList_To_v1alpha1(a.(*ipam.IPPrefixClassList), b.(*IPPrefixClassList)) }, }, { (*ipam.IPPrefix)(nil), (*IPPrefix)(nil), - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_v1alpha1_IPPrefix_To_ipam(a.(*IPPrefix), b.(*ipam.IPPrefix)) }, - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_ipam_IPPrefix_To_v1alpha1(a.(*ipam.IPPrefix), b.(*IPPrefix)) }, }, { (*ipam.IPPrefixList)(nil), (*IPPrefixList)(nil), - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_v1alpha1_IPPrefixList_To_ipam(a.(*IPPrefixList), b.(*ipam.IPPrefixList)) }, - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_ipam_IPPrefixList_To_v1alpha1(a.(*ipam.IPPrefixList), b.(*IPPrefixList)) }, }, { (*ipam.IPPrefixClaim)(nil), (*IPPrefixClaim)(nil), - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_v1alpha1_IPPrefixClaim_To_ipam(a.(*IPPrefixClaim), b.(*ipam.IPPrefixClaim)) }, - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_ipam_IPPrefixClaim_To_v1alpha1(a.(*ipam.IPPrefixClaim), b.(*IPPrefixClaim)) }, }, { (*ipam.IPPrefixClaimList)(nil), (*IPPrefixClaimList)(nil), - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_v1alpha1_IPPrefixClaimList_To_ipam(a.(*IPPrefixClaimList), b.(*ipam.IPPrefixClaimList)) }, - func(a, b interface{}, sc conversion.Scope) error { + func(a, b any, sc conversion.Scope) error { return convert_ipam_IPPrefixClaimList_To_v1alpha1(a.(*ipam.IPPrefixClaimList), b.(*IPPrefixClaimList)) }, }, - { - (*ipam.IPAddress)(nil), (*IPAddress)(nil), - func(a, b interface{}, sc conversion.Scope) error { - return convert_v1alpha1_IPAddress_To_ipam(a.(*IPAddress), b.(*ipam.IPAddress)) - }, - func(a, b interface{}, sc conversion.Scope) error { - return convert_ipam_IPAddress_To_v1alpha1(a.(*ipam.IPAddress), b.(*IPAddress)) - }, - }, - { - (*ipam.IPAddressList)(nil), (*IPAddressList)(nil), - func(a, b interface{}, sc conversion.Scope) error { - return convert_v1alpha1_IPAddressList_To_ipam(a.(*IPAddressList), b.(*ipam.IPAddressList)) - }, - func(a, b interface{}, sc conversion.Scope) error { - return convert_ipam_IPAddressList_To_v1alpha1(a.(*ipam.IPAddressList), b.(*IPAddressList)) - }, - }, - { - (*ipam.IPAddressClaim)(nil), (*IPAddressClaim)(nil), - func(a, b interface{}, sc conversion.Scope) error { - return convert_v1alpha1_IPAddressClaim_To_ipam(a.(*IPAddressClaim), b.(*ipam.IPAddressClaim)) - }, - func(a, b interface{}, sc conversion.Scope) error { - return convert_ipam_IPAddressClaim_To_v1alpha1(a.(*ipam.IPAddressClaim), b.(*IPAddressClaim)) - }, - }, - { - (*ipam.IPAddressClaimList)(nil), (*IPAddressClaimList)(nil), - func(a, b interface{}, sc conversion.Scope) error { - return convert_v1alpha1_IPAddressClaimList_To_ipam(a.(*IPAddressClaimList), b.(*ipam.IPAddressClaimList)) - }, - func(a, b interface{}, sc conversion.Scope) error { - return convert_ipam_IPAddressClaimList_To_v1alpha1(a.(*ipam.IPAddressClaimList), b.(*IPAddressClaimList)) - }, - }, } for _, p := range pairs { if err := s.AddGeneratedConversionFunc(p.external, p.internal, p.toInternal); err != nil { diff --git a/pkg/apis/ipam/v1alpha1/conversion_impl.go b/pkg/apis/ipam/v1alpha1/conversion_impl.go index ab1d84a..b024d17 100644 --- a/pkg/apis/ipam/v1alpha1/conversion_impl.go +++ b/pkg/apis/ipam/v1alpha1/conversion_impl.go @@ -171,8 +171,9 @@ func convert_v1alpha1_IPPrefixClass_To_ipam(in *IPPrefixClass, out *ipam.IPPrefi out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = ipam.IPPrefixClassSpec{ - Visibility: in.Spec.Visibility, - DefaultAllocation: toIpamAllocation(in.Spec.DefaultAllocation), + RequiresVerification: in.Spec.RequiresVerification, + Visibility: in.Spec.Visibility, + DefaultAllocation: toIpamAllocation(in.Spec.DefaultAllocation), } return nil } @@ -180,8 +181,9 @@ func convert_ipam_IPPrefixClass_To_v1alpha1(in *ipam.IPPrefixClass, out *IPPrefi out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = IPPrefixClassSpec{ - Visibility: in.Spec.Visibility, - DefaultAllocation: toV1Allocation(in.Spec.DefaultAllocation), + RequiresVerification: in.Spec.RequiresVerification, + Visibility: in.Spec.Visibility, + DefaultAllocation: toV1Allocation(in.Spec.DefaultAllocation), } return nil } @@ -331,128 +333,3 @@ func convert_ipam_IPPrefixClaimList_To_v1alpha1(in *ipam.IPPrefixClaimList, out return nil } -// ---------------------------------------------------------------------------- -// IPAddress -// ---------------------------------------------------------------------------- - -func convert_v1alpha1_IPAddress_To_ipam(in *IPAddress, out *ipam.IPAddress) error { - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = ipam.IPAddressSpec{ - Address: in.Spec.Address, - IPFamily: ipam.IPFamily(in.Spec.IPFamily), - PrefixRef: ipam.LocalRef{Name: in.Spec.PrefixRef.Name}, - ClaimRef: toIpamLocalRef(in.Spec.ClaimRef), - } - out.Status = ipam.IPAddressStatus{Conditions: toIpamConditions(in.Status.Conditions)} - return nil -} -func convert_ipam_IPAddress_To_v1alpha1(in *ipam.IPAddress, out *IPAddress) error { - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = IPAddressSpec{ - Address: in.Spec.Address, - IPFamily: IPFamily(in.Spec.IPFamily), - PrefixRef: LocalRef{Name: in.Spec.PrefixRef.Name}, - ClaimRef: toV1LocalRef(in.Spec.ClaimRef), - } - out.Status = IPAddressStatus{Conditions: toIpamConditions(in.Status.Conditions)} - return nil -} - -func convert_v1alpha1_IPAddressList_To_ipam(in *IPAddressList, out *ipam.IPAddressList) error { - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - out.Items = make([]ipam.IPAddress, len(in.Items)) - for i := range in.Items { - if err := convert_v1alpha1_IPAddress_To_ipam(&in.Items[i], &out.Items[i]); err != nil { - return err - } - } - } - return nil -} -func convert_ipam_IPAddressList_To_v1alpha1(in *ipam.IPAddressList, out *IPAddressList) error { - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - out.Items = make([]IPAddress, len(in.Items)) - for i := range in.Items { - if err := convert_ipam_IPAddress_To_v1alpha1(&in.Items[i], &out.Items[i]); err != nil { - return err - } - } - } - return nil -} - -// ---------------------------------------------------------------------------- -// IPAddressClaim -// ---------------------------------------------------------------------------- - -func convert_v1alpha1_IPAddressClaim_To_ipam(in *IPAddressClaim, out *ipam.IPAddressClaim) error { - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = ipam.IPAddressClaimSpec{ - IPFamily: ipam.IPFamily(in.Spec.IPFamily), - PrefixSelector: toIpamPrefixSelector(in.Spec.PrefixSelector), - PrefixRef: toIpamNamespacedRef(in.Spec.PrefixRef), - ReclaimPolicy: ipam.ReclaimPolicy(in.Spec.ReclaimPolicy), - OwnerRef: toIpamObjectRef(in.Spec.OwnerRef), - } - out.Status = ipam.IPAddressClaimStatus{ - Phase: ipam.ClaimPhase(in.Status.Phase), - AllocatedIP: in.Status.AllocatedIP, - BoundAddressRef: toIpamLocalRef(in.Status.BoundAddressRef), - Conditions: toIpamConditions(in.Status.Conditions), - } - return nil -} -func convert_ipam_IPAddressClaim_To_v1alpha1(in *ipam.IPAddressClaim, out *IPAddressClaim) error { - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = IPAddressClaimSpec{ - IPFamily: IPFamily(in.Spec.IPFamily), - PrefixSelector: toV1PrefixSelector(in.Spec.PrefixSelector), - PrefixRef: toV1NamespacedRef(in.Spec.PrefixRef), - ReclaimPolicy: ReclaimPolicy(in.Spec.ReclaimPolicy), - OwnerRef: toV1ObjectRef(in.Spec.OwnerRef), - } - out.Status = IPAddressClaimStatus{ - Phase: ClaimPhase(in.Status.Phase), - AllocatedIP: in.Status.AllocatedIP, - BoundAddressRef: toV1LocalRef(in.Status.BoundAddressRef), - Conditions: toIpamConditions(in.Status.Conditions), - } - return nil -} - -func convert_v1alpha1_IPAddressClaimList_To_ipam(in *IPAddressClaimList, out *ipam.IPAddressClaimList) error { - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - out.Items = make([]ipam.IPAddressClaim, len(in.Items)) - for i := range in.Items { - if err := convert_v1alpha1_IPAddressClaim_To_ipam(&in.Items[i], &out.Items[i]); err != nil { - return err - } - } - } - return nil -} -func convert_ipam_IPAddressClaimList_To_v1alpha1(in *ipam.IPAddressClaimList, out *IPAddressClaimList) error { - out.TypeMeta = in.TypeMeta - out.ListMeta = in.ListMeta - if in.Items != nil { - out.Items = make([]IPAddressClaim, len(in.Items)) - for i := range in.Items { - if err := convert_ipam_IPAddressClaim_To_v1alpha1(&in.Items[i], &out.Items[i]); err != nil { - return err - } - } - } - return nil -} - - diff --git a/pkg/apis/ipam/v1alpha1/protobuf.go b/pkg/apis/ipam/v1alpha1/protobuf.go index cefe784..a3c2297 100644 --- a/pkg/apis/ipam/v1alpha1/protobuf.go +++ b/pkg/apis/ipam/v1alpha1/protobuf.go @@ -33,17 +33,4 @@ func (in *IPPrefixClaim) Unmarshal(data []byte) error { return json.Unmarsha func (in *IPPrefixClaimList) Marshal() ([]byte, error) { return json.Marshal(in) } func (in *IPPrefixClaimList) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } -// --- IPAddress --- - -func (in *IPAddress) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddress) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } -func (in *IPAddressList) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddressList) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } - -// --- IPAddressClaim --- - -func (in *IPAddressClaim) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddressClaim) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } -func (in *IPAddressClaimList) Marshal() ([]byte, error) { return json.Marshal(in) } -func (in *IPAddressClaimList) Unmarshal(data []byte) error { return json.Unmarshal(data, in) } diff --git a/pkg/apis/ipam/v1alpha1/register.go b/pkg/apis/ipam/v1alpha1/register.go index 74d736e..dfa2e49 100644 --- a/pkg/apis/ipam/v1alpha1/register.go +++ b/pkg/apis/ipam/v1alpha1/register.go @@ -27,8 +27,6 @@ func addKnownTypes(scheme *runtime.Scheme) error { &IPPrefixClass{}, &IPPrefixClassList{}, &IPPrefix{}, &IPPrefixList{}, &IPPrefixClaim{}, &IPPrefixClaimList{}, - &IPAddress{}, &IPAddressList{}, - &IPAddressClaim{}, &IPAddressClaimList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/ipam/v1alpha1/types.go b/pkg/apis/ipam/v1alpha1/types.go index 5b5ba8c..c6d8719 100644 --- a/pkg/apis/ipam/v1alpha1/types.go +++ b/pkg/apis/ipam/v1alpha1/types.go @@ -136,6 +136,10 @@ type IPPrefixClass struct { } type IPPrefixClassSpec struct { + // RequiresVerification indicates that IP prefixes borrowing from this + // class must be verified before they can be used (e.g. BYOIP flows). + // +optional + RequiresVerification bool `json:"requiresVerification,omitempty"` // Visibility controls cross-project access semantics for IPPrefix // pools that reference this class. "platform" pools are platform-only // (callers see them only when running with platform scope); @@ -283,102 +287,4 @@ type IPPrefixClaimList struct { Items []IPPrefixClaim `json:"items"` } -// ---------------------------------------------------------------------------- -// IPAddress -// ---------------------------------------------------------------------------- - -// +kubebuilder:object:root=true -// +kubebuilder:resource:shortName=ipa -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:name="Address",type=string,JSONPath=`.spec.address` -// +kubebuilder:printcolumn:name="Family",type=string,JSONPath=`.spec.ipFamily` -// +kubebuilder:printcolumn:name="Prefix",type=string,JSONPath=`.spec.prefixRef.name` -// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type IPAddress struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec IPAddressSpec `json:"spec,omitempty"` - Status IPAddressStatus `json:"status,omitempty"` -} - -type IPAddressSpec struct { - Address string `json:"address"` - IPFamily IPFamily `json:"ipFamily"` - PrefixRef LocalRef `json:"prefixRef"` - // +optional - ClaimRef *LocalRef `json:"claimRef,omitempty"` -} - -type IPAddressStatus struct { - // +optional - // +listType=map - // +listMapKey=type - Conditions []metav1.Condition `json:"conditions,omitempty"` -} - -// +kubebuilder:object:root=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type IPAddressList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []IPAddress `json:"items"` -} - -// ---------------------------------------------------------------------------- -// IPAddressClaim -// ---------------------------------------------------------------------------- - -// +kubebuilder:object:root=true -// +kubebuilder:resource:shortName=ipac -// +kubebuilder:subresource:status -// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase` -// +kubebuilder:printcolumn:name="IP",type=string,JSONPath=`.status.allocatedIP` -// +kubebuilder:printcolumn:name="Pool",type=string,JSONPath=`.spec.prefixRef.name` -// +kubebuilder:printcolumn:name="Family",type=string,JSONPath=`.spec.ipFamily` -// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type IPAddressClaim struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec IPAddressClaimSpec `json:"spec,omitempty"` - Status IPAddressClaimStatus `json:"status,omitempty"` -} - -type IPAddressClaimSpec struct { - IPFamily IPFamily `json:"ipFamily"` - // +optional - PrefixSelector *PrefixSelector `json:"prefixSelector,omitempty"` - // +optional - PrefixRef *NamespacedRef `json:"prefixRef,omitempty"` - // +optional - ReclaimPolicy ReclaimPolicy `json:"reclaimPolicy,omitempty"` - // +optional - OwnerRef *ObjectRef `json:"ownerRef,omitempty"` -} - -type IPAddressClaimStatus struct { - // +optional - Phase ClaimPhase `json:"phase,omitempty"` - // +optional - AllocatedIP string `json:"allocatedIP,omitempty"` - // +optional - BoundAddressRef *LocalRef `json:"boundAddressRef,omitempty"` - // +optional - // +listType=map - // +listMapKey=type - Conditions []metav1.Condition `json:"conditions,omitempty"` -} - -// +kubebuilder:object:root=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type IPAddressClaimList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []IPAddressClaim `json:"items"` -} diff --git a/pkg/apis/ipam/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/ipam/v1alpha1/zz_generated.deepcopy.go index 2b7fbbc..e8d679f 100644 --- a/pkg/apis/ipam/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/ipam/v1alpha1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated // Code generated by deepcopy-gen. DO NOT EDIT. @@ -26,232 +25,6 @@ func (in *AllocationSpec) DeepCopy() *AllocationSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddress) DeepCopyInto(out *IPAddress) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddress. -func (in *IPAddress) DeepCopy() *IPAddress { - if in == nil { - return nil - } - out := new(IPAddress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddress) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaim) DeepCopyInto(out *IPAddressClaim) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaim. -func (in *IPAddressClaim) DeepCopy() *IPAddressClaim { - if in == nil { - return nil - } - out := new(IPAddressClaim) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddressClaim) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaimList) DeepCopyInto(out *IPAddressClaimList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]IPAddressClaim, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaimList. -func (in *IPAddressClaimList) DeepCopy() *IPAddressClaimList { - if in == nil { - return nil - } - out := new(IPAddressClaimList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddressClaimList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaimSpec) DeepCopyInto(out *IPAddressClaimSpec) { - *out = *in - if in.PrefixSelector != nil { - in, out := &in.PrefixSelector, &out.PrefixSelector - *out = new(PrefixSelector) - (*in).DeepCopyInto(*out) - } - if in.PrefixRef != nil { - in, out := &in.PrefixRef, &out.PrefixRef - *out = new(NamespacedRef) - (*in).DeepCopyInto(*out) - } - if in.OwnerRef != nil { - in, out := &in.OwnerRef, &out.OwnerRef - *out = new(ObjectRef) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaimSpec. -func (in *IPAddressClaimSpec) DeepCopy() *IPAddressClaimSpec { - if in == nil { - return nil - } - out := new(IPAddressClaimSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaimStatus) DeepCopyInto(out *IPAddressClaimStatus) { - *out = *in - if in.BoundAddressRef != nil { - in, out := &in.BoundAddressRef, &out.BoundAddressRef - *out = new(LocalRef) - **out = **in - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaimStatus. -func (in *IPAddressClaimStatus) DeepCopy() *IPAddressClaimStatus { - if in == nil { - return nil - } - out := new(IPAddressClaimStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressList) DeepCopyInto(out *IPAddressList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]IPAddress, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressList. -func (in *IPAddressList) DeepCopy() *IPAddressList { - if in == nil { - return nil - } - out := new(IPAddressList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddressList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressSpec) DeepCopyInto(out *IPAddressSpec) { - *out = *in - out.PrefixRef = in.PrefixRef - if in.ClaimRef != nil { - in, out := &in.ClaimRef, &out.ClaimRef - *out = new(LocalRef) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressSpec. -func (in *IPAddressSpec) DeepCopy() *IPAddressSpec { - if in == nil { - return nil - } - out := new(IPAddressSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressStatus) DeepCopyInto(out *IPAddressStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressStatus. -func (in *IPAddressStatus) DeepCopy() *IPAddressStatus { - if in == nil { - return nil - } - out := new(IPAddressStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPPrefix) DeepCopyInto(out *IPPrefix) { *out = *in diff --git a/pkg/apis/ipam/zz_generated.deepcopy.go b/pkg/apis/ipam/zz_generated.deepcopy.go index 5cc362e..ac33840 100644 --- a/pkg/apis/ipam/zz_generated.deepcopy.go +++ b/pkg/apis/ipam/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated // Code generated by deepcopy-gen. DO NOT EDIT. @@ -26,232 +25,6 @@ func (in *AllocationSpec) DeepCopy() *AllocationSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddress) DeepCopyInto(out *IPAddress) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddress. -func (in *IPAddress) DeepCopy() *IPAddress { - if in == nil { - return nil - } - out := new(IPAddress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddress) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaim) DeepCopyInto(out *IPAddressClaim) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaim. -func (in *IPAddressClaim) DeepCopy() *IPAddressClaim { - if in == nil { - return nil - } - out := new(IPAddressClaim) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddressClaim) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaimList) DeepCopyInto(out *IPAddressClaimList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]IPAddressClaim, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaimList. -func (in *IPAddressClaimList) DeepCopy() *IPAddressClaimList { - if in == nil { - return nil - } - out := new(IPAddressClaimList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddressClaimList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaimSpec) DeepCopyInto(out *IPAddressClaimSpec) { - *out = *in - if in.PrefixSelector != nil { - in, out := &in.PrefixSelector, &out.PrefixSelector - *out = new(PrefixSelector) - (*in).DeepCopyInto(*out) - } - if in.PrefixRef != nil { - in, out := &in.PrefixRef, &out.PrefixRef - *out = new(NamespacedRef) - (*in).DeepCopyInto(*out) - } - if in.OwnerRef != nil { - in, out := &in.OwnerRef, &out.OwnerRef - *out = new(ObjectRef) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaimSpec. -func (in *IPAddressClaimSpec) DeepCopy() *IPAddressClaimSpec { - if in == nil { - return nil - } - out := new(IPAddressClaimSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressClaimStatus) DeepCopyInto(out *IPAddressClaimStatus) { - *out = *in - if in.BoundAddressRef != nil { - in, out := &in.BoundAddressRef, &out.BoundAddressRef - *out = new(LocalRef) - **out = **in - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressClaimStatus. -func (in *IPAddressClaimStatus) DeepCopy() *IPAddressClaimStatus { - if in == nil { - return nil - } - out := new(IPAddressClaimStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressList) DeepCopyInto(out *IPAddressList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]IPAddress, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressList. -func (in *IPAddressList) DeepCopy() *IPAddressList { - if in == nil { - return nil - } - out := new(IPAddressList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IPAddressList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressSpec) DeepCopyInto(out *IPAddressSpec) { - *out = *in - out.PrefixRef = in.PrefixRef - if in.ClaimRef != nil { - in, out := &in.ClaimRef, &out.ClaimRef - *out = new(LocalRef) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressSpec. -func (in *IPAddressSpec) DeepCopy() *IPAddressSpec { - if in == nil { - return nil - } - out := new(IPAddressSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IPAddressStatus) DeepCopyInto(out *IPAddressStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressStatus. -func (in *IPAddressStatus) DeepCopy() *IPAddressStatus { - if in == nil { - return nil - } - out := new(IPAddressStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPPrefix) DeepCopyInto(out *IPPrefix) { *out = *in diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipaddress.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipaddress.go deleted file mode 100644 index c647b66..0000000 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipaddress.go +++ /dev/null @@ -1,34 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - ipamv1alpha1 "go.miloapis.com/ipam/pkg/client/clientset/versioned/typed/ipam/v1alpha1" - gentype "k8s.io/client-go/gentype" -) - -// fakeIPAddresses implements IPAddressInterface -type fakeIPAddresses struct { - *gentype.FakeClientWithList[*v1alpha1.IPAddress, *v1alpha1.IPAddressList] - Fake *FakeIpamV1alpha1 -} - -func newFakeIPAddresses(fake *FakeIpamV1alpha1, namespace string) ipamv1alpha1.IPAddressInterface { - return &fakeIPAddresses{ - gentype.NewFakeClientWithList[*v1alpha1.IPAddress, *v1alpha1.IPAddressList]( - fake.Fake, - namespace, - v1alpha1.SchemeGroupVersion.WithResource("ipaddresses"), - v1alpha1.SchemeGroupVersion.WithKind("IPAddress"), - func() *v1alpha1.IPAddress { return &v1alpha1.IPAddress{} }, - func() *v1alpha1.IPAddressList { return &v1alpha1.IPAddressList{} }, - func(dst, src *v1alpha1.IPAddressList) { dst.ListMeta = src.ListMeta }, - func(list *v1alpha1.IPAddressList) []*v1alpha1.IPAddress { return gentype.ToPointerSlice(list.Items) }, - func(list *v1alpha1.IPAddressList, items []*v1alpha1.IPAddress) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipaddressclaim.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipaddressclaim.go deleted file mode 100644 index 73e13f5..0000000 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipaddressclaim.go +++ /dev/null @@ -1,36 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package fake - -import ( - v1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - ipamv1alpha1 "go.miloapis.com/ipam/pkg/client/clientset/versioned/typed/ipam/v1alpha1" - gentype "k8s.io/client-go/gentype" -) - -// fakeIPAddressClaims implements IPAddressClaimInterface -type fakeIPAddressClaims struct { - *gentype.FakeClientWithList[*v1alpha1.IPAddressClaim, *v1alpha1.IPAddressClaimList] - Fake *FakeIpamV1alpha1 -} - -func newFakeIPAddressClaims(fake *FakeIpamV1alpha1, namespace string) ipamv1alpha1.IPAddressClaimInterface { - return &fakeIPAddressClaims{ - gentype.NewFakeClientWithList[*v1alpha1.IPAddressClaim, *v1alpha1.IPAddressClaimList]( - fake.Fake, - namespace, - v1alpha1.SchemeGroupVersion.WithResource("ipaddressclaims"), - v1alpha1.SchemeGroupVersion.WithKind("IPAddressClaim"), - func() *v1alpha1.IPAddressClaim { return &v1alpha1.IPAddressClaim{} }, - func() *v1alpha1.IPAddressClaimList { return &v1alpha1.IPAddressClaimList{} }, - func(dst, src *v1alpha1.IPAddressClaimList) { dst.ListMeta = src.ListMeta }, - func(list *v1alpha1.IPAddressClaimList) []*v1alpha1.IPAddressClaim { - return gentype.ToPointerSlice(list.Items) - }, - func(list *v1alpha1.IPAddressClaimList, items []*v1alpha1.IPAddressClaim) { - list.Items = gentype.FromPointerSlice(items) - }, - ), - fake, - } -} diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipam_client.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipam_client.go index 2e4ae04..39b19f7 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipam_client.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipam_client.go @@ -12,14 +12,6 @@ type FakeIpamV1alpha1 struct { *testing.Fake } -func (c *FakeIpamV1alpha1) IPAddresses(namespace string) v1alpha1.IPAddressInterface { - return newFakeIPAddresses(c, namespace) -} - -func (c *FakeIpamV1alpha1) IPAddressClaims(namespace string) v1alpha1.IPAddressClaimInterface { - return newFakeIPAddressClaims(c, namespace) -} - func (c *FakeIpamV1alpha1) IPPrefixes() v1alpha1.IPPrefixInterface { return newFakeIPPrefixes(c) } diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefix.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefix.go index 87f1595..f9eaf6f 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefix.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefix.go @@ -16,7 +16,7 @@ type fakeIPPrefixes struct { func newFakeIPPrefixes(fake *FakeIpamV1alpha1) ipamv1alpha1.IPPrefixInterface { return &fakeIPPrefixes{ - gentype.NewFakeClientWithList[*v1alpha1.IPPrefix, *v1alpha1.IPPrefixList]( + gentype.NewFakeClientWithList( fake.Fake, "", v1alpha1.SchemeGroupVersion.WithResource("ipprefixes"), diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclaim.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclaim.go index b83d10d..4e0cbd1 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclaim.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclaim.go @@ -16,7 +16,7 @@ type fakeIPPrefixClaims struct { func newFakeIPPrefixClaims(fake *FakeIpamV1alpha1, namespace string) ipamv1alpha1.IPPrefixClaimInterface { return &fakeIPPrefixClaims{ - gentype.NewFakeClientWithList[*v1alpha1.IPPrefixClaim, *v1alpha1.IPPrefixClaimList]( + gentype.NewFakeClientWithList( fake.Fake, namespace, v1alpha1.SchemeGroupVersion.WithResource("ipprefixclaims"), diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclass.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclass.go index 7007031..1050021 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclass.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/fake/fake_ipprefixclass.go @@ -16,7 +16,7 @@ type fakeIPPrefixClasses struct { func newFakeIPPrefixClasses(fake *FakeIpamV1alpha1) ipamv1alpha1.IPPrefixClassInterface { return &fakeIPPrefixClasses{ - gentype.NewFakeClientWithList[*v1alpha1.IPPrefixClass, *v1alpha1.IPPrefixClassList]( + gentype.NewFakeClientWithList( fake.Fake, "", v1alpha1.SchemeGroupVersion.WithResource("ipprefixclasses"), diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/generated_expansion.go index 29c66d6..814ba22 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/generated_expansion.go @@ -2,12 +2,8 @@ package v1alpha1 -type IPAddressExpansion interface{} +type IPPrefixExpansion any -type IPAddressClaimExpansion interface{} +type IPPrefixClaimExpansion any -type IPPrefixExpansion interface{} - -type IPPrefixClaimExpansion interface{} - -type IPPrefixClassExpansion interface{} +type IPPrefixClassExpansion any diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipaddress.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipaddress.go deleted file mode 100644 index fef71a9..0000000 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipaddress.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - - ipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - scheme "go.miloapis.com/ipam/pkg/client/clientset/versioned/scheme" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - gentype "k8s.io/client-go/gentype" -) - -// IPAddressesGetter has a method to return a IPAddressInterface. -// A group's client should implement this interface. -type IPAddressesGetter interface { - IPAddresses(namespace string) IPAddressInterface -} - -// IPAddressInterface has methods to work with IPAddress resources. -type IPAddressInterface interface { - Create(ctx context.Context, iPAddress *ipamv1alpha1.IPAddress, opts v1.CreateOptions) (*ipamv1alpha1.IPAddress, error) - Update(ctx context.Context, iPAddress *ipamv1alpha1.IPAddress, opts v1.UpdateOptions) (*ipamv1alpha1.IPAddress, error) - // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, iPAddress *ipamv1alpha1.IPAddress, opts v1.UpdateOptions) (*ipamv1alpha1.IPAddress, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*ipamv1alpha1.IPAddress, error) - List(ctx context.Context, opts v1.ListOptions) (*ipamv1alpha1.IPAddressList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *ipamv1alpha1.IPAddress, err error) - IPAddressExpansion -} - -// iPAddresses implements IPAddressInterface -type iPAddresses struct { - *gentype.ClientWithList[*ipamv1alpha1.IPAddress, *ipamv1alpha1.IPAddressList] -} - -// newIPAddresses returns a IPAddresses -func newIPAddresses(c *IpamV1alpha1Client, namespace string) *iPAddresses { - return &iPAddresses{ - gentype.NewClientWithList[*ipamv1alpha1.IPAddress, *ipamv1alpha1.IPAddressList]( - "ipaddresses", - c.RESTClient(), - scheme.ParameterCodec, - namespace, - func() *ipamv1alpha1.IPAddress { return &ipamv1alpha1.IPAddress{} }, - func() *ipamv1alpha1.IPAddressList { return &ipamv1alpha1.IPAddressList{} }, - ), - } -} diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipaddressclaim.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipaddressclaim.go deleted file mode 100644 index 9baf7af..0000000 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipaddressclaim.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by client-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - - ipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - scheme "go.miloapis.com/ipam/pkg/client/clientset/versioned/scheme" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - gentype "k8s.io/client-go/gentype" -) - -// IPAddressClaimsGetter has a method to return a IPAddressClaimInterface. -// A group's client should implement this interface. -type IPAddressClaimsGetter interface { - IPAddressClaims(namespace string) IPAddressClaimInterface -} - -// IPAddressClaimInterface has methods to work with IPAddressClaim resources. -type IPAddressClaimInterface interface { - Create(ctx context.Context, iPAddressClaim *ipamv1alpha1.IPAddressClaim, opts v1.CreateOptions) (*ipamv1alpha1.IPAddressClaim, error) - Update(ctx context.Context, iPAddressClaim *ipamv1alpha1.IPAddressClaim, opts v1.UpdateOptions) (*ipamv1alpha1.IPAddressClaim, error) - // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). - UpdateStatus(ctx context.Context, iPAddressClaim *ipamv1alpha1.IPAddressClaim, opts v1.UpdateOptions) (*ipamv1alpha1.IPAddressClaim, error) - Delete(ctx context.Context, name string, opts v1.DeleteOptions) error - DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error - Get(ctx context.Context, name string, opts v1.GetOptions) (*ipamv1alpha1.IPAddressClaim, error) - List(ctx context.Context, opts v1.ListOptions) (*ipamv1alpha1.IPAddressClaimList, error) - Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *ipamv1alpha1.IPAddressClaim, err error) - IPAddressClaimExpansion -} - -// iPAddressClaims implements IPAddressClaimInterface -type iPAddressClaims struct { - *gentype.ClientWithList[*ipamv1alpha1.IPAddressClaim, *ipamv1alpha1.IPAddressClaimList] -} - -// newIPAddressClaims returns a IPAddressClaims -func newIPAddressClaims(c *IpamV1alpha1Client, namespace string) *iPAddressClaims { - return &iPAddressClaims{ - gentype.NewClientWithList[*ipamv1alpha1.IPAddressClaim, *ipamv1alpha1.IPAddressClaimList]( - "ipaddressclaims", - c.RESTClient(), - scheme.ParameterCodec, - namespace, - func() *ipamv1alpha1.IPAddressClaim { return &ipamv1alpha1.IPAddressClaim{} }, - func() *ipamv1alpha1.IPAddressClaimList { return &ipamv1alpha1.IPAddressClaimList{} }, - ), - } -} diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipam_client.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipam_client.go index f2ff1eb..b9b7e16 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipam_client.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipam_client.go @@ -12,8 +12,6 @@ import ( type IpamV1alpha1Interface interface { RESTClient() rest.Interface - IPAddressesGetter - IPAddressClaimsGetter IPPrefixesGetter IPPrefixClaimsGetter IPPrefixClassesGetter @@ -24,14 +22,6 @@ type IpamV1alpha1Client struct { restClient rest.Interface } -func (c *IpamV1alpha1Client) IPAddresses(namespace string) IPAddressInterface { - return newIPAddresses(c, namespace) -} - -func (c *IpamV1alpha1Client) IPAddressClaims(namespace string) IPAddressClaimInterface { - return newIPAddressClaims(c, namespace) -} - func (c *IpamV1alpha1Client) IPPrefixes() IPPrefixInterface { return newIPPrefixes(c) } diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefix.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefix.go index c91f1ab..97ae81a 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefix.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefix.go @@ -42,7 +42,7 @@ type iPPrefixes struct { // newIPPrefixes returns a IPPrefixes func newIPPrefixes(c *IpamV1alpha1Client) *iPPrefixes { return &iPPrefixes{ - gentype.NewClientWithList[*ipamv1alpha1.IPPrefix, *ipamv1alpha1.IPPrefixList]( + gentype.NewClientWithList( "ipprefixes", c.RESTClient(), scheme.ParameterCodec, diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclaim.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclaim.go index d8887da..95abfe4 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclaim.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclaim.go @@ -42,7 +42,7 @@ type iPPrefixClaims struct { // newIPPrefixClaims returns a IPPrefixClaims func newIPPrefixClaims(c *IpamV1alpha1Client, namespace string) *iPPrefixClaims { return &iPPrefixClaims{ - gentype.NewClientWithList[*ipamv1alpha1.IPPrefixClaim, *ipamv1alpha1.IPPrefixClaimList]( + gentype.NewClientWithList( "ipprefixclaims", c.RESTClient(), scheme.ParameterCodec, diff --git a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclass.go b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclass.go index b469000..1151df4 100644 --- a/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclass.go +++ b/pkg/client/clientset/versioned/typed/ipam/v1alpha1/ipprefixclass.go @@ -40,7 +40,7 @@ type iPPrefixClasses struct { // newIPPrefixClasses returns a IPPrefixClasses func newIPPrefixClasses(c *IpamV1alpha1Client) *iPPrefixClasses { return &iPPrefixClasses{ - gentype.NewClientWithList[*ipamv1alpha1.IPPrefixClass, *ipamv1alpha1.IPPrefixClassList]( + gentype.NewClientWithList( "ipprefixclasses", c.RESTClient(), scheme.ParameterCodec, diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 37321b1..5692609 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -37,10 +37,6 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=ipam.miloapis.com, Version=v1alpha1 - case v1alpha1.SchemeGroupVersion.WithResource("ipaddresses"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Ipam().V1alpha1().IPAddresses().Informer()}, nil - case v1alpha1.SchemeGroupVersion.WithResource("ipaddressclaims"): - return &genericInformer{resource: resource.GroupResource(), informer: f.Ipam().V1alpha1().IPAddressClaims().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("ipprefixes"): return &genericInformer{resource: resource.GroupResource(), informer: f.Ipam().V1alpha1().IPPrefixes().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("ipprefixclaims"): diff --git a/pkg/client/informers/externalversions/ipam/v1alpha1/interface.go b/pkg/client/informers/externalversions/ipam/v1alpha1/interface.go index 1818bc0..f253759 100644 --- a/pkg/client/informers/externalversions/ipam/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/ipam/v1alpha1/interface.go @@ -8,10 +8,6 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { - // IPAddresses returns a IPAddressInformer. - IPAddresses() IPAddressInformer - // IPAddressClaims returns a IPAddressClaimInformer. - IPAddressClaims() IPAddressClaimInformer // IPPrefixes returns a IPPrefixInformer. IPPrefixes() IPPrefixInformer // IPPrefixClaims returns a IPPrefixClaimInformer. @@ -31,16 +27,6 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } -// IPAddresses returns a IPAddressInformer. -func (v *version) IPAddresses() IPAddressInformer { - return &iPAddressInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} - -// IPAddressClaims returns a IPAddressClaimInformer. -func (v *version) IPAddressClaims() IPAddressClaimInformer { - return &iPAddressClaimInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} -} - // IPPrefixes returns a IPPrefixInformer. func (v *version) IPPrefixes() IPPrefixInformer { return &iPPrefixInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/ipam/v1alpha1/ipaddress.go b/pkg/client/informers/externalversions/ipam/v1alpha1/ipaddress.go deleted file mode 100644 index 545445b..0000000 --- a/pkg/client/informers/externalversions/ipam/v1alpha1/ipaddress.go +++ /dev/null @@ -1,86 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - time "time" - - apisipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - versioned "go.miloapis.com/ipam/pkg/client/clientset/versioned" - internalinterfaces "go.miloapis.com/ipam/pkg/client/informers/externalversions/internalinterfaces" - ipamv1alpha1 "go.miloapis.com/ipam/pkg/client/listers/ipam/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" -) - -// IPAddressInformer provides access to a shared informer and lister for -// IPAddresses. -type IPAddressInformer interface { - Informer() cache.SharedIndexInformer - Lister() ipamv1alpha1.IPAddressLister -} - -type iPAddressInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewIPAddressInformer constructs a new informer for IPAddress type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewIPAddressInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIPAddressInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredIPAddressInformer constructs a new informer for IPAddress type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredIPAddressInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddresses(namespace).List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddresses(namespace).Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddresses(namespace).List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddresses(namespace).Watch(ctx, options) - }, - }, client), - &apisipamv1alpha1.IPAddress{}, - resyncPeriod, - indexers, - ) -} - -func (f *iPAddressInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIPAddressInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *iPAddressInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apisipamv1alpha1.IPAddress{}, f.defaultInformer) -} - -func (f *iPAddressInformer) Lister() ipamv1alpha1.IPAddressLister { - return ipamv1alpha1.NewIPAddressLister(f.Informer().GetIndexer()) -} diff --git a/pkg/client/informers/externalversions/ipam/v1alpha1/ipaddressclaim.go b/pkg/client/informers/externalversions/ipam/v1alpha1/ipaddressclaim.go deleted file mode 100644 index d4a1db3..0000000 --- a/pkg/client/informers/externalversions/ipam/v1alpha1/ipaddressclaim.go +++ /dev/null @@ -1,86 +0,0 @@ -// Code generated by informer-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - context "context" - time "time" - - apisipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - versioned "go.miloapis.com/ipam/pkg/client/clientset/versioned" - internalinterfaces "go.miloapis.com/ipam/pkg/client/informers/externalversions/internalinterfaces" - ipamv1alpha1 "go.miloapis.com/ipam/pkg/client/listers/ipam/v1alpha1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - watch "k8s.io/apimachinery/pkg/watch" - cache "k8s.io/client-go/tools/cache" -) - -// IPAddressClaimInformer provides access to a shared informer and lister for -// IPAddressClaims. -type IPAddressClaimInformer interface { - Informer() cache.SharedIndexInformer - Lister() ipamv1alpha1.IPAddressClaimLister -} - -type iPAddressClaimInformer struct { - factory internalinterfaces.SharedInformerFactory - tweakListOptions internalinterfaces.TweakListOptionsFunc - namespace string -} - -// NewIPAddressClaimInformer constructs a new informer for IPAddressClaim type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewIPAddressClaimInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredIPAddressClaimInformer(client, namespace, resyncPeriod, indexers, nil) -} - -// NewFilteredIPAddressClaimInformer constructs a new informer for IPAddressClaim type. -// Always prefer using an informer factory to get a shared informer instead of getting an independent -// one. This reduces memory footprint and number of connections to the server. -func NewFilteredIPAddressClaimInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( - cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddressClaims(namespace).List(context.Background(), options) - }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddressClaims(namespace).Watch(context.Background(), options) - }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddressClaims(namespace).List(ctx, options) - }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { - if tweakListOptions != nil { - tweakListOptions(&options) - } - return client.IpamV1alpha1().IPAddressClaims(namespace).Watch(ctx, options) - }, - }, client), - &apisipamv1alpha1.IPAddressClaim{}, - resyncPeriod, - indexers, - ) -} - -func (f *iPAddressClaimInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredIPAddressClaimInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) -} - -func (f *iPAddressClaimInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&apisipamv1alpha1.IPAddressClaim{}, f.defaultInformer) -} - -func (f *iPAddressClaimInformer) Lister() ipamv1alpha1.IPAddressClaimLister { - return ipamv1alpha1.NewIPAddressClaimLister(f.Informer().GetIndexer()) -} diff --git a/pkg/client/listers/ipam/v1alpha1/expansion_generated.go b/pkg/client/listers/ipam/v1alpha1/expansion_generated.go index 980bc0c..e507fc5 100644 --- a/pkg/client/listers/ipam/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/ipam/v1alpha1/expansion_generated.go @@ -2,34 +2,18 @@ package v1alpha1 -// IPAddressListerExpansion allows custom methods to be added to -// IPAddressLister. -type IPAddressListerExpansion interface{} - -// IPAddressNamespaceListerExpansion allows custom methods to be added to -// IPAddressNamespaceLister. -type IPAddressNamespaceListerExpansion interface{} - -// IPAddressClaimListerExpansion allows custom methods to be added to -// IPAddressClaimLister. -type IPAddressClaimListerExpansion interface{} - -// IPAddressClaimNamespaceListerExpansion allows custom methods to be added to -// IPAddressClaimNamespaceLister. -type IPAddressClaimNamespaceListerExpansion interface{} - // IPPrefixListerExpansion allows custom methods to be added to // IPPrefixLister. -type IPPrefixListerExpansion interface{} +type IPPrefixListerExpansion any // IPPrefixClaimListerExpansion allows custom methods to be added to // IPPrefixClaimLister. -type IPPrefixClaimListerExpansion interface{} +type IPPrefixClaimListerExpansion any // IPPrefixClaimNamespaceListerExpansion allows custom methods to be added to // IPPrefixClaimNamespaceLister. -type IPPrefixClaimNamespaceListerExpansion interface{} +type IPPrefixClaimNamespaceListerExpansion any // IPPrefixClassListerExpansion allows custom methods to be added to // IPPrefixClassLister. -type IPPrefixClassListerExpansion interface{} +type IPPrefixClassListerExpansion any diff --git a/pkg/client/listers/ipam/v1alpha1/ipaddress.go b/pkg/client/listers/ipam/v1alpha1/ipaddress.go deleted file mode 100644 index ff274dc..0000000 --- a/pkg/client/listers/ipam/v1alpha1/ipaddress.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - ipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" -) - -// IPAddressLister helps list IPAddresses. -// All objects returned here must be treated as read-only. -type IPAddressLister interface { - // List lists all IPAddresses in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*ipamv1alpha1.IPAddress, err error) - // IPAddresses returns an object that can list and get IPAddresses. - IPAddresses(namespace string) IPAddressNamespaceLister - IPAddressListerExpansion -} - -// iPAddressLister implements the IPAddressLister interface. -type iPAddressLister struct { - listers.ResourceIndexer[*ipamv1alpha1.IPAddress] -} - -// NewIPAddressLister returns a new IPAddressLister. -func NewIPAddressLister(indexer cache.Indexer) IPAddressLister { - return &iPAddressLister{listers.New[*ipamv1alpha1.IPAddress](indexer, ipamv1alpha1.Resource("ipaddress"))} -} - -// IPAddresses returns an object that can list and get IPAddresses. -func (s *iPAddressLister) IPAddresses(namespace string) IPAddressNamespaceLister { - return iPAddressNamespaceLister{listers.NewNamespaced[*ipamv1alpha1.IPAddress](s.ResourceIndexer, namespace)} -} - -// IPAddressNamespaceLister helps list and get IPAddresses. -// All objects returned here must be treated as read-only. -type IPAddressNamespaceLister interface { - // List lists all IPAddresses in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*ipamv1alpha1.IPAddress, err error) - // Get retrieves the IPAddress from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*ipamv1alpha1.IPAddress, error) - IPAddressNamespaceListerExpansion -} - -// iPAddressNamespaceLister implements the IPAddressNamespaceLister -// interface. -type iPAddressNamespaceLister struct { - listers.ResourceIndexer[*ipamv1alpha1.IPAddress] -} diff --git a/pkg/client/listers/ipam/v1alpha1/ipaddressclaim.go b/pkg/client/listers/ipam/v1alpha1/ipaddressclaim.go deleted file mode 100644 index 29f7f63..0000000 --- a/pkg/client/listers/ipam/v1alpha1/ipaddressclaim.go +++ /dev/null @@ -1,54 +0,0 @@ -// Code generated by lister-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - ipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1" - labels "k8s.io/apimachinery/pkg/labels" - listers "k8s.io/client-go/listers" - cache "k8s.io/client-go/tools/cache" -) - -// IPAddressClaimLister helps list IPAddressClaims. -// All objects returned here must be treated as read-only. -type IPAddressClaimLister interface { - // List lists all IPAddressClaims in the indexer. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*ipamv1alpha1.IPAddressClaim, err error) - // IPAddressClaims returns an object that can list and get IPAddressClaims. - IPAddressClaims(namespace string) IPAddressClaimNamespaceLister - IPAddressClaimListerExpansion -} - -// iPAddressClaimLister implements the IPAddressClaimLister interface. -type iPAddressClaimLister struct { - listers.ResourceIndexer[*ipamv1alpha1.IPAddressClaim] -} - -// NewIPAddressClaimLister returns a new IPAddressClaimLister. -func NewIPAddressClaimLister(indexer cache.Indexer) IPAddressClaimLister { - return &iPAddressClaimLister{listers.New[*ipamv1alpha1.IPAddressClaim](indexer, ipamv1alpha1.Resource("ipaddressclaim"))} -} - -// IPAddressClaims returns an object that can list and get IPAddressClaims. -func (s *iPAddressClaimLister) IPAddressClaims(namespace string) IPAddressClaimNamespaceLister { - return iPAddressClaimNamespaceLister{listers.NewNamespaced[*ipamv1alpha1.IPAddressClaim](s.ResourceIndexer, namespace)} -} - -// IPAddressClaimNamespaceLister helps list and get IPAddressClaims. -// All objects returned here must be treated as read-only. -type IPAddressClaimNamespaceLister interface { - // List lists all IPAddressClaims in the indexer for a given namespace. - // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*ipamv1alpha1.IPAddressClaim, err error) - // Get retrieves the IPAddressClaim from the indexer for a given namespace and name. - // Objects returned here must be treated as read-only. - Get(name string) (*ipamv1alpha1.IPAddressClaim, error) - IPAddressClaimNamespaceListerExpansion -} - -// iPAddressClaimNamespaceLister implements the IPAddressClaimNamespaceLister -// interface. -type iPAddressClaimNamespaceLister struct { - listers.ResourceIndexer[*ipamv1alpha1.IPAddressClaim] -} diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 309cc07..89b3db0 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated // Code generated by openapi-gen. DO NOT EDIT. @@ -17,14 +16,6 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.AllocationSpec": schema_pkg_apis_ipam_v1alpha1_AllocationSpec(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddress": schema_pkg_apis_ipam_v1alpha1_IPAddress(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaim": schema_pkg_apis_ipam_v1alpha1_IPAddressClaim(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimList": schema_pkg_apis_ipam_v1alpha1_IPAddressClaimList(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimSpec": schema_pkg_apis_ipam_v1alpha1_IPAddressClaimSpec(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimStatus": schema_pkg_apis_ipam_v1alpha1_IPAddressClaimStatus(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressList": schema_pkg_apis_ipam_v1alpha1_IPAddressList(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressSpec": schema_pkg_apis_ipam_v1alpha1_IPAddressSpec(ref), - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressStatus": schema_pkg_apis_ipam_v1alpha1_IPAddressStatus(ref), "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefix": schema_pkg_apis_ipam_v1alpha1_IPPrefix(ref), "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClaim": schema_pkg_apis_ipam_v1alpha1_IPPrefixClaim(ref), "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClaimList": schema_pkg_apis_ipam_v1alpha1_IPPrefixClaimList(ref), @@ -132,364 +123,6 @@ func schema_pkg_apis_ipam_v1alpha1_AllocationSpec(ref common.ReferenceCallback) } } -func schema_pkg_apis_ipam_v1alpha1_IPAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressStatus"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressSpec", "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressStatus", v1.ObjectMeta{}.OpenAPIModelName()}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressClaim(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimStatus"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimSpec", "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaimStatus", v1.ObjectMeta{}.OpenAPIModelName()}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressClaimList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v1.ListMeta{}.OpenAPIModelName()), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaim"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddressClaim", v1.ListMeta{}.OpenAPIModelName()}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressClaimSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "ipFamily": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "prefixSelector": { - SchemaProps: spec.SchemaProps{ - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.PrefixSelector"), - }, - }, - "prefixRef": { - SchemaProps: spec.SchemaProps{ - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.NamespacedRef"), - }, - }, - "reclaimPolicy": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "ownerRef": { - SchemaProps: spec.SchemaProps{ - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.ObjectRef"), - }, - }, - }, - Required: []string{"ipFamily"}, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.NamespacedRef", "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.ObjectRef", "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.PrefixSelector"}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "phase": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "allocatedIP": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "boundAddressRef": { - SchemaProps: spec.SchemaProps{ - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.LocalRef"), - }, - }, - "conditions": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ - "type", - }, - "x-kubernetes-list-type": "map", - }, - }, - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v1.Condition{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.LocalRef", v1.Condition{}.OpenAPIModelName()}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v1.ListMeta{}.OpenAPIModelName()), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddress"), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPAddress", v1.ListMeta{}.OpenAPIModelName()}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "address": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "ipFamily": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "prefixRef": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.LocalRef"), - }, - }, - "claimRef": { - SchemaProps: spec.SchemaProps{ - Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.LocalRef"), - }, - }, - }, - Required: []string{"address", "ipFamily", "prefixRef"}, - }, - }, - Dependencies: []string{ - "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.LocalRef"}, - } -} - -func schema_pkg_apis_ipam_v1alpha1_IPAddressStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "conditions": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ - "type", - }, - "x-kubernetes-list-type": "map", - }, - }, - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(v1.Condition{}.OpenAPIModelName()), - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - v1.Condition{}.OpenAPIModelName()}, - } -} - func schema_pkg_apis_ipam_v1alpha1_IPPrefix(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -513,19 +146,19 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefix(ref common.ReferenceCallback) common }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixStatus"), }, }, @@ -559,19 +192,19 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClaim(ref common.ReferenceCallback) c }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClaimSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClaimStatus"), }, }, @@ -605,7 +238,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClaimList(ref common.ReferenceCallbac }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -615,7 +248,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClaimList(ref common.ReferenceCallbac Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClaim"), }, }, @@ -713,7 +346,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClaimStatus(ref common.ReferenceCallb "conditions": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ + "x-kubernetes-list-map-keys": []any{ "type", }, "x-kubernetes-list-type": "map", @@ -724,7 +357,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClaimStatus(ref common.ReferenceCallb Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.Condition{}.OpenAPIModelName()), }, }, @@ -762,13 +395,13 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClass(ref common.ReferenceCallback) c }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClassSpec"), }, }, @@ -803,7 +436,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClassList(ref common.ReferenceCallbac }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -813,7 +446,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClassList(ref common.ReferenceCallbac Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixClass"), }, }, @@ -850,7 +483,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixClassSpec(ref common.ReferenceCallbac }, "defaultAllocation": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.AllocationSpec"), }, }, @@ -884,7 +517,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixList(ref common.ReferenceCallback) co }, "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -894,7 +527,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixList(ref common.ReferenceCallback) co Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefix"), }, }, @@ -933,13 +566,13 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixSpec(ref common.ReferenceCallback) co }, "classRef": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.LocalRef"), }, }, "allocation": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.AllocationSpec"), }, }, @@ -977,14 +610,14 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixStatus(ref common.ReferenceCallback) }, "capacity": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.PrefixCapacity"), }, }, "conditions": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ + "x-kubernetes-list-map-keys": []any{ "type", }, "x-kubernetes-list-type": "map", @@ -995,7 +628,7 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixStatus(ref common.ReferenceCallback) Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.Condition{}.OpenAPIModelName()), }, }, @@ -1019,13 +652,13 @@ func schema_pkg_apis_ipam_v1alpha1_IPPrefixTemplate(ref common.ReferenceCallback Properties: map[string]spec.Schema{ "metadata": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, "spec": { SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref("go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1.IPPrefixSpec"), }, }, @@ -1198,7 +831,7 @@ func schema_pkg_apis_ipam_v1alpha1_PrefixSelector(ref common.ReferenceCallback) Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.LabelSelectorRequirement{}.OpenAPIModelName()), }, }, @@ -1307,7 +940,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.GroupVersionForDiscovery{}.OpenAPIModelName()), }, }, @@ -1317,7 +950,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA "preferredVersion": { SchemaProps: spec.SchemaProps{ Description: "preferredVersion is the version preferred by the API server, which probably is the storage version.", - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.GroupVersionForDiscovery{}.OpenAPIModelName()), }, }, @@ -1333,7 +966,7 @@ func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenA Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ServerAddressByClientCIDR{}.OpenAPIModelName()), }, }, @@ -1382,7 +1015,7 @@ func schema_pkg_apis_meta_v1_APIGroupList(ref common.ReferenceCallback) common.O Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.APIGroup{}.OpenAPIModelName()), }, }, @@ -1561,7 +1194,7 @@ func schema_pkg_apis_meta_v1_APIResourceList(ref common.ReferenceCallback) commo Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.APIResource{}.OpenAPIModelName()), }, }, @@ -1630,7 +1263,7 @@ func schema_pkg_apis_meta_v1_APIVersions(ref common.ReferenceCallback) common.Op Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ServerAddressByClientCIDR{}.OpenAPIModelName()), }, }, @@ -2267,7 +1900,7 @@ func schema_pkg_apis_meta_v1_LabelSelector(ref common.ReferenceCallback) common. Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.LabelSelectorRequirement{}.OpenAPIModelName()), }, }, @@ -2361,7 +1994,7 @@ func schema_pkg_apis_meta_v1_List(ref common.ReferenceCallback) common.OpenAPIDe "metadata": { SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -2724,7 +2357,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope "ownerReferences": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ - "x-kubernetes-list-map-keys": []interface{}{ + "x-kubernetes-list-map-keys": []any{ "uid", }, "x-kubernetes-list-type": "map", @@ -2738,7 +2371,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.OwnerReference{}.OpenAPIModelName()), }, }, @@ -2778,7 +2411,7 @@ func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.Ope Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ManagedFieldsEntry{}.OpenAPIModelName()), }, }, @@ -2882,7 +2515,7 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadata(ref common.ReferenceCallback) "metadata": { SchemaProps: spec.SchemaProps{ Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ObjectMeta{}.OpenAPIModelName()), }, }, @@ -2918,7 +2551,7 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallb "metadata": { SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -2929,7 +2562,7 @@ func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallb Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.PartialObjectMetadata{}.OpenAPIModelName()), }, }, @@ -3161,7 +2794,7 @@ func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPI "metadata": { SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -3288,7 +2921,7 @@ func schema_pkg_apis_meta_v1_StatusDetails(ref common.ReferenceCallback) common. Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.StatusCause{}.OpenAPIModelName()), }, }, @@ -3334,7 +2967,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID "metadata": { SchemaProps: spec.SchemaProps{ Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.ListMeta{}.OpenAPIModelName()), }, }, @@ -3350,7 +2983,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.TableColumnDefinition{}.OpenAPIModelName()), }, }, @@ -3369,7 +3002,7 @@ func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPID Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.TableRow{}.OpenAPIModelName()), }, }, @@ -3511,7 +3144,7 @@ func schema_pkg_apis_meta_v1_TableRow(ref common.ReferenceCallback) common.OpenA Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, + Default: map[string]any{}, Ref: ref(v1.TableRowCondition{}.OpenAPIModelName()), }, }, diff --git a/test/e2e/address-allocation/assertions/assert-claim-1-deleted.yaml b/test/e2e/address-allocation/assertions/assert-claim-1-deleted.yaml deleted file mode 100644 index ada7f86..0000000 --- a/test/e2e/address-allocation/assertions/assert-claim-1-deleted.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim -metadata: - name: addr-claim-1 - namespace: ($namespace) diff --git a/test/e2e/address-allocation/chainsaw-test.yaml b/test/e2e/address-allocation/chainsaw-test.yaml deleted file mode 100644 index fe829d8..0000000 --- a/test/e2e/address-allocation/chainsaw-test.yaml +++ /dev/null @@ -1,225 +0,0 @@ -apiVersion: chainsaw.kyverno.io/v1alpha1 -kind: Test -metadata: - name: address-allocation -spec: - description: | - Synchronous IPAddressClaim allocation: - - First claim binds with status.allocatedIP inside the pool CIDR. - - Second claim binds with a distinct IP. - - Filling the pool then attempting one more claim returns HTTP 507. - - Releasing one bound claim makes the IP reusable. - - Pool sized as a /29 (8 addresses) instead of the spec's /28 (16). A /29 is - still large enough to demonstrate distinct allocation, exhaustion and reuse - while keeping the apply/wait loops short. The exhaustion suite uses /31 for - the same reason; this preserves that "tight pool" idiom. - - steps: - - name: setup-address-pool - description: Create class + IPPrefix (10.50.0.0/29, /32 only) — 8 addresses - try: - - create: - file: test-data/class.yaml - - create: - file: test-data/prefix.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: addr-pool - timeout: 30s - for: - condition: - name: Ready - value: 'True' - - - name: first-claim-bound - description: | - IPAddressClaim succeeds synchronously with status.phase=Bound and - status.allocatedIP non-empty and inside 10.50.0.0/29. - try: - - create: - file: test-data/claim-1.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: addr-claim-1 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - set -e - ip=$(kubectl get ipaddressclaim -n "$NAMESPACE" addr-claim-1 -o jsonpath='{.status.allocatedIP}') - if [ -z "$ip" ]; then - echo "FAIL: empty allocatedIP"; exit 1 - fi - # /29 covers 10.50.0.0 .. 10.50.0.7 - if ! echo "$ip" | grep -qE '^10\.50\.0\.[0-7]$'; then - echo "FAIL: $ip not in 10.50.0.0/29"; exit 1 - fi - echo "OK addr-claim-1 allocatedIP=$ip in 10.50.0.0/29" - check: - ($error == null): true - (contains($stdout, 'OK addr-claim-1 allocatedIP=')): true - - - name: second-claim-distinct-ip - description: Second claim binds with an IP different from claim-1. - try: - - create: - file: test-data/claim-2.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: addr-claim-2 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - set -e - ip1=$(kubectl get ipaddressclaim -n "$NAMESPACE" addr-claim-1 -o jsonpath='{.status.allocatedIP}') - ip2=$(kubectl get ipaddressclaim -n "$NAMESPACE" addr-claim-2 -o jsonpath='{.status.allocatedIP}') - if [ -z "$ip2" ]; then - echo "FAIL: empty allocatedIP for addr-claim-2"; exit 1 - fi - if ! echo "$ip2" | grep -qE '^10\.50\.0\.[0-7]$'; then - echo "FAIL: $ip2 not in 10.50.0.0/29"; exit 1 - fi - if [ "$ip1" = "$ip2" ]; then - echo "FAIL: addr-claim-2 reused $ip1"; exit 1 - fi - echo "OK addr-claim-1=$ip1 addr-claim-2=$ip2 distinct" - check: - ($error == null): true - (contains($stdout, 'OK addr-claim-1=')): true - - - name: fill-and-overflow-rejected-507 - description: | - Apply six more claims to fill the /29; the ninth overflow claim must - fail HTTP 507 (Insufficient Storage). - try: - - create: - file: test-data/claims-fill.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - namespace: ($namespace) - selector: addr-test=true - timeout: 60s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - set -e - count=$(kubectl get ipaddressclaim -n "$NAMESPACE" -l addr-test=true \ - -o jsonpath='{.items[*].status.allocatedIP}' | tr ' ' '\n' | awk 'NF>0' | sort -u | awk 'END{print NR}') - if [ "$count" != "8" ]; then - echo "FAIL: expected 8 unique IPs, got $count" - kubectl get ipaddressclaim -n "$NAMESPACE" -l addr-test=true \ - -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatedIP}{"\n"}{end}' - exit 1 - fi - echo "OK 8 unique IPs allocated across the /29" - check: - ($error == null): true - (contains($stdout, 'OK 8 unique IPs')): true - - create: - file: test-data/claim-overflow.yaml - expect: - - check: - ($error != null): true - (contains($error, '507') || contains($error, 'Insufficient Storage') || contains($error, 'pool exhausted')): true - - - name: release-and-reallocate - description: | - Delete addr-claim-1 and confirm a fresh claim binds (released IP is - reusable). The new claim must take the same IP that addr-claim-1 held, - since that's the only free slot in the /29. - try: - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - set -e - freed=$(kubectl get ipaddressclaim -n "$NAMESPACE" addr-claim-1 -o jsonpath='{.status.allocatedIP}') - if [ -z "$freed" ]; then - echo "FAIL: addr-claim-1 has no allocatedIP to record"; exit 1 - fi - echo "$freed" > /tmp/addr-freed-ip - echo "recorded freed IP: $freed" - check: - ($error == null): true - - delete: - ref: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: addr-claim-1 - namespace: ($namespace) - - error: - file: assertions/assert-claim-1-deleted.yaml - - create: - file: test-data/claim-reuse.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: addr-claim-reuse - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - set -e - new_ip=$(kubectl get ipaddressclaim -n "$NAMESPACE" addr-claim-reuse -o jsonpath='{.status.allocatedIP}') - freed=$(cat /tmp/addr-freed-ip) - if [ -z "$new_ip" ]; then - echo "FAIL: empty allocatedIP for addr-claim-reuse"; exit 1 - fi - if [ "$new_ip" != "$freed" ]; then - echo "FAIL: reuse claim got $new_ip but expected freed slot $freed" - exit 1 - fi - echo "OK released IP $freed reused by addr-claim-reuse" - check: - ($error == null): true - (contains($stdout, 'OK released IP')): true - - finally: - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - kubectl delete ipaddressclaim -n "$NAMESPACE" \ - addr-claim-1 addr-claim-2 addr-claim-3 addr-claim-4 \ - addr-claim-5 addr-claim-6 addr-claim-7 addr-claim-8 \ - addr-claim-overflow addr-claim-reuse --ignore-not-found >/dev/null 2>&1 || true - kubectl delete ipprefix addr-pool --ignore-not-found >/dev/null 2>&1 || true - kubectl delete ipprefixclass addr-class --ignore-not-found >/dev/null 2>&1 || true - echo "address-allocation cleanup done" - check: - ($error == null): true diff --git a/test/e2e/address-allocation/test-data/class.yaml b/test/e2e/address-allocation/test-data/class.yaml deleted file mode 100644 index dfa44d9..0000000 --- a/test/e2e/address-allocation/test-data/class.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPPrefixClass -metadata: - name: addr-class -spec: - requiresVerification: false - visibility: consumer - defaultAllocation: - minPrefixLength: 32 - maxPrefixLength: 32 - strategy: FirstFit diff --git a/test/e2e/address-allocation/test-data/prefix.yaml b/test/e2e/address-allocation/test-data/prefix.yaml deleted file mode 100644 index 0593740..0000000 --- a/test/e2e/address-allocation/test-data/prefix.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPPrefix -metadata: - name: addr-pool -spec: - cidr: 10.50.0.0/29 - ipFamily: IPv4 - classRef: - name: addr-class - allocation: - minPrefixLength: 32 - maxPrefixLength: 32 - strategy: FirstFit diff --git a/test/e2e/host-address-allocation/00-setup.yaml b/test/e2e/host-address-allocation/00-setup.yaml new file mode 100644 index 0000000..4b54979 --- /dev/null +++ b/test/e2e/host-address-allocation/00-setup.yaml @@ -0,0 +1,55 @@ +# IPv4 /32-only class for host-route allocation. +apiVersion: ipam.miloapis.com/v1alpha1 +kind: IPPrefixClass +metadata: + name: host-class-v4 +spec: + requiresVerification: false + visibility: consumer + defaultAllocation: + minPrefixLength: 32 + maxPrefixLength: 32 + strategy: FirstFit +--- +# IPv4 /29 parent pool: 10.50.1.0 – 10.50.1.7 (8 host addresses). +apiVersion: ipam.miloapis.com/v1alpha1 +kind: IPPrefix +metadata: + name: host-pool-v4 +spec: + cidr: 10.50.1.0/29 + ipFamily: IPv4 + classRef: + name: host-class-v4 + allocation: + minPrefixLength: 32 + maxPrefixLength: 32 + strategy: FirstFit +--- +# IPv6 /128-only class for host-route allocation. +apiVersion: ipam.miloapis.com/v1alpha1 +kind: IPPrefixClass +metadata: + name: host-class-v6 +spec: + requiresVerification: false + visibility: consumer + defaultAllocation: + minPrefixLength: 128 + maxPrefixLength: 128 + strategy: FirstFit +--- +# IPv6 /126 parent pool: 2001:db8::/126 (4 host addresses). +apiVersion: ipam.miloapis.com/v1alpha1 +kind: IPPrefix +metadata: + name: host-pool-v6 +spec: + cidr: 2001:db8::/126 + ipFamily: IPv6 + classRef: + name: host-class-v6 + allocation: + minPrefixLength: 128 + maxPrefixLength: 128 + strategy: FirstFit diff --git a/test/e2e/address-allocation/test-data/claim-1.yaml b/test/e2e/host-address-allocation/01-ipv4-host-claim.yaml similarity index 57% rename from test/e2e/address-allocation/test-data/claim-1.yaml rename to test/e2e/host-address-allocation/01-ipv4-host-claim.yaml index 6999703..34042cf 100644 --- a/test/e2e/address-allocation/test-data/claim-1.yaml +++ b/test/e2e/host-address-allocation/01-ipv4-host-claim.yaml @@ -1,12 +1,13 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-1 + name: host-claim-v4-1 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete diff --git a/test/e2e/address-allocation/test-data/claim-2.yaml b/test/e2e/host-address-allocation/02-ipv4-uniqueness.yaml similarity index 57% rename from test/e2e/address-allocation/test-data/claim-2.yaml rename to test/e2e/host-address-allocation/02-ipv4-uniqueness.yaml index b363125..a4c125f 100644 --- a/test/e2e/address-allocation/test-data/claim-2.yaml +++ b/test/e2e/host-address-allocation/02-ipv4-uniqueness.yaml @@ -1,12 +1,13 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-2 + name: host-claim-v4-2 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete diff --git a/test/e2e/address-allocation/test-data/claims-fill.yaml b/test/e2e/host-address-allocation/03-exhaustion.yaml similarity index 51% rename from test/e2e/address-allocation/test-data/claims-fill.yaml rename to test/e2e/host-address-allocation/03-exhaustion.yaml index 7a0d0e9..bdc42dc 100644 --- a/test/e2e/address-allocation/test-data/claims-fill.yaml +++ b/test/e2e/host-address-allocation/03-exhaustion.yaml @@ -1,79 +1,86 @@ -# Six additional claims. Combined with claim-1 and claim-2 these saturate the -# /29 (8 addresses), leaving the pool fully allocated for the exhaustion step. +# Claims host-claim-v4-3 through host-claim-v4-8. +# Combined with host-claim-v4-1 and host-claim-v4-2 these saturate the /29 +# (8 addresses), leaving the pool fully allocated for the overflow check. apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-3 + name: host-claim-v4-3 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete --- apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-4 + name: host-claim-v4-4 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete --- apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-5 + name: host-claim-v4-5 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete --- apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-6 + name: host-claim-v4-6 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete --- apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-7 + name: host-claim-v4-7 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete --- apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-8 + name: host-claim-v4-8 namespace: ($namespace) labels: - addr-test: "true" + host-test: "true" spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete diff --git a/test/e2e/address-allocation/test-data/claim-reuse.yaml b/test/e2e/host-address-allocation/04-ipv6-host-claim.yaml similarity index 50% rename from test/e2e/address-allocation/test-data/claim-reuse.yaml rename to test/e2e/host-address-allocation/04-ipv6-host-claim.yaml index 9dc6bdb..31f855b 100644 --- a/test/e2e/address-allocation/test-data/claim-reuse.yaml +++ b/test/e2e/host-address-allocation/04-ipv6-host-claim.yaml @@ -1,12 +1,11 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-reuse + name: host-claim-v6-1 namespace: ($namespace) - labels: - addr-test: "true" spec: - ipFamily: IPv4 + ipFamily: IPv6 + prefixLength: 128 prefixRef: - name: addr-pool + name: host-pool-v6 reclaimPolicy: Delete diff --git a/test/e2e/host-address-allocation/chainsaw-test.yaml b/test/e2e/host-address-allocation/chainsaw-test.yaml new file mode 100644 index 0000000..52419a7 --- /dev/null +++ b/test/e2e/host-address-allocation/chainsaw-test.yaml @@ -0,0 +1,307 @@ +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: host-address-allocation +spec: + description: | + Host-route allocation via IPPrefixClaim with prefixLength: 32 (IPv4) or + prefixLength: 128 (IPv6). Single-address allocation no longer uses a + dedicated IPAddressClaim resource; callers use IPPrefixClaim instead. + + Tests: + 1. IPv4 /32 bind — /29 pool (10.50.1.0/29, 8 addresses); claim /32; + assert Bound and allocatedCIDR in 10.50.1.[0-7]/32. + 2. IPv4 /32 unique — second /32 from the same pool is distinct. + 3. Pool exhaustion — fill all 8 slots; ninth claim fails HTTP 507; + pool status.availableAddresses == 0. + 4. IPv6 /128 bind — /126 pool (2001:db8::/126, 4 addresses); claim /128; + assert Bound and a /128 allocatedCIDR. + + steps: + # ── Setup ─────────────────────────────────────────────────────────────── + - name: setup-pools + description: | + Create IPPrefixClass + two pools: + host-pool-v4 (10.50.1.0/29, IPv4, /32 only) + host-pool-v6 (2001:db8::/126, IPv6, /128 only) + try: + - apply: + file: 00-setup.yaml + - script: + timeout: 45s + content: | + set -e + for pool in host-pool-v4 host-pool-v6; do + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix "$pool" \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: $pool not Ready after 30s" + exit 1 + fi + done + echo "all host pools Ready" + check: + ($error == null): true + + # ── Step 1: IPv4 /32 bind ──────────────────────────────────────────────── + - name: ipv4-host-claim-bound + description: | + IPPrefixClaim with prefixLength: 32 binds synchronously. + status.allocatedCIDR must be a /32 within 10.50.1.0/29. + try: + - apply: + file: 01-ipv4-host-claim.yaml + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v4-1 \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: host-claim-v4-1 not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + cidr=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v4-1 \ + -o jsonpath='{.status.allocatedCIDR}') + if [ -z "$cidr" ]; then + echo "FAIL: empty allocatedCIDR"; exit 1 + fi + # Verify the prefix length is /32 + prefix_len=$(echo "$cidr" | cut -d/ -f2) + if [ "$prefix_len" != "32" ]; then + echo "FAIL: expected /32, got /$prefix_len (cidr=$cidr)"; exit 1 + fi + # Verify it is within 10.50.1.0/29 (10.50.1.0 .. 10.50.1.7) + host=$(echo "$cidr" | cut -d/ -f1) + python3 -c " + import ipaddress, sys + child = ipaddress.ip_network('$cidr') + parent = ipaddress.ip_network('10.50.1.0/29') + if not child.subnet_of(parent): + print(f'FAIL: {child} not in 10.50.1.0/29') + sys.exit(1) + print(f'OK {child} is in 10.50.1.0/29') + " + check: + ($error == null): true + (contains($stdout, 'OK ')): true + + # ── Step 2: IPv4 /32 uniqueness ────────────────────────────────────────── + - name: ipv4-host-uniqueness + description: | + Second IPPrefixClaim with prefixLength: 32 receives a distinct /32 + from the same pool; the two allocatedCIDRs must not overlap. + try: + - apply: + file: 02-ipv4-uniqueness.yaml + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v4-2 \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: host-claim-v4-2 not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + cidr1=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v4-1 \ + -o jsonpath='{.status.allocatedCIDR}') + cidr2=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v4-2 \ + -o jsonpath='{.status.allocatedCIDR}') + if [ -z "$cidr2" ]; then + echo "FAIL: empty allocatedCIDR for host-claim-v4-2"; exit 1 + fi + if [ "$cidr1" = "$cidr2" ]; then + echo "FAIL: both claims got the same CIDR $cidr1"; exit 1 + fi + python3 -c " + import ipaddress, sys + n1 = ipaddress.ip_network('$cidr1') + n2 = ipaddress.ip_network('$cidr2') + if n1.overlaps(n2): + print(f'FAIL: {n1} and {n2} overlap') + sys.exit(1) + print(f'OK {n1} and {n2} are distinct') + " + check: + ($error == null): true + (contains($stdout, 'OK ')): true + + # ── Step 3: Pool exhaustion → HTTP 507 ─────────────────────────────────── + - name: exhaustion-507 + description: | + Fill all 8 host slots in the /29 pool, then assert the ninth claim + fails with HTTP 507 (Insufficient Storage). Also asserts that the + pool reports status.availableAddresses == 0. + try: + - apply: + file: 03-exhaustion.yaml + - script: + timeout: 75s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 60); do + count=$(kubectl get ipprefixclaim -n "$NAMESPACE" -l host-test=true \ + -o jsonpath='{.items[*].status.phase}' 2>/dev/null | tr ' ' '\n' | grep -c "^Bound$" || echo "0") + if [ "$count" = "8" ]; then break; fi + sleep 1 + done + if [ "$count" != "8" ]; then + echo "FAIL: only $count/8 host claims Bound after 60s" + exit 1 + fi + check: + ($error == null): true + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + count=$(kubectl get ipprefixclaim -n "$NAMESPACE" -l host-test=true \ + -o jsonpath='{.items[*].status.allocatedCIDR}' \ + | tr ' ' '\n' | awk 'NF>0' | sort -u | awk 'END{print NR}') + if [ "$count" != "8" ]; then + echo "FAIL: expected 8 unique /32 CIDRs, got $count" + kubectl get ipprefixclaim -n "$NAMESPACE" -l host-test=true \ + -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.allocatedCIDR}{"\n"}{end}' + exit 1 + fi + echo "OK 8 unique /32 addresses allocated across the /29" + check: + ($error == null): true + (contains($stdout, 'OK 8 unique /32 addresses')): true + - script: + content: | + set -e + avail=$(kubectl get ipprefix host-pool-v4 \ + -o jsonpath='{.status.availableAddresses}' 2>/dev/null || echo "") + echo "pool status.availableAddresses=${avail}" + if [ -n "$avail" ] && [ "$avail" != "0" ]; then + echo "FAIL: expected availableAddresses=0, got $avail" + exit 1 + fi + echo "OK pool availableAddresses is 0 (or unset — pool exhausted)" + check: + ($error == null): true + (contains($stdout, 'OK pool availableAddresses')): true + - create: + file: test-data/claim-overflow.yaml + expect: + - check: + ($error != null): true + (contains($error, '507') || contains($error, 'Insufficient Storage') || contains($error, 'pool exhausted')): true + + # ── Step 4: IPv6 /128 bind ─────────────────────────────────────────────── + - name: ipv6-host-claim-bound + description: | + IPPrefixClaim with prefixLength: 128 and ipFamily: IPv6 binds + synchronously from the 2001:db8::/126 pool (4 addresses). + status.allocatedCIDR must be a /128 subnet of 2001:db8::/126. + try: + - apply: + file: 04-ipv6-host-claim.yaml + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v6-1 \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: host-claim-v6-1 not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + cidr=$(kubectl get ipprefixclaim -n "$NAMESPACE" host-claim-v6-1 \ + -o jsonpath='{.status.allocatedCIDR}') + if [ -z "$cidr" ]; then + echo "FAIL: empty allocatedCIDR"; exit 1 + fi + prefix_len=$(echo "$cidr" | cut -d/ -f2) + if [ "$prefix_len" != "128" ]; then + echo "FAIL: expected /128, got /$prefix_len (cidr=$cidr)"; exit 1 + fi + python3 -c " + import ipaddress, sys + child = ipaddress.ip_network('$cidr') + parent = ipaddress.ip_network('2001:db8::/126') + if not child.subnet_of(parent): + print(f'FAIL: {child} not in 2001:db8::/126') + sys.exit(1) + print(f'OK {child} is in 2001:db8::/126') + " + check: + ($error == null): true + (contains($stdout, 'OK ')): true + + finally: + - script: + env: + - name: NAMESPACE + value: ($namespace) + content: | + kubectl delete ipprefixclaim -n "$NAMESPACE" \ + host-claim-v4-1 host-claim-v4-2 \ + host-claim-v4-3 host-claim-v4-4 host-claim-v4-5 \ + host-claim-v4-6 host-claim-v4-7 host-claim-v4-8 \ + host-claim-v4-overflow \ + host-claim-v6-1 \ + --ignore-not-found >/dev/null 2>&1 || true + kubectl delete ipprefix host-pool-v4 host-pool-v6 \ + --ignore-not-found >/dev/null 2>&1 || true + kubectl delete ipprefixclass host-class-v4 host-class-v6 \ + --ignore-not-found >/dev/null 2>&1 || true + echo "host-address-allocation cleanup done" + check: + ($error == null): true diff --git a/test/e2e/address-allocation/test-data/claim-overflow.yaml b/test/e2e/host-address-allocation/test-data/claim-overflow.yaml similarity index 59% rename from test/e2e/address-allocation/test-data/claim-overflow.yaml rename to test/e2e/host-address-allocation/test-data/claim-overflow.yaml index 9f13fa8..615f883 100644 --- a/test/e2e/address-allocation/test-data/claim-overflow.yaml +++ b/test/e2e/host-address-allocation/test-data/claim-overflow.yaml @@ -1,10 +1,11 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: - name: addr-claim-overflow + name: host-claim-v4-overflow namespace: ($namespace) spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: - name: addr-pool + name: host-pool-v4 reclaimPolicy: Delete diff --git a/test/e2e/multi-tenant/chainsaw-test.yaml b/test/e2e/multi-tenant/chainsaw-test.yaml index d84f62d..f3dc282 100644 --- a/test/e2e/multi-tenant/chainsaw-test.yaml +++ b/test/e2e/multi-tenant/chainsaw-test.yaml @@ -39,45 +39,23 @@ spec: file: resources/pools.yaml - create: file: resources/rbac.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: mt-alpha-pool - timeout: 30s - for: - condition: - name: Ready - value: 'True' - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: mt-beta-pool - timeout: 30s - for: - condition: - name: Ready - value: 'True' - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: mt-shared-pool - timeout: 30s - for: - condition: - name: Ready - value: 'True' - finally: - # Mirror the cluster-scoped resources created in this step so the suite - # leaves no leaks behind. The classes, pools, ClusterRole and - # ClusterRoleBinding are all cluster-scoped, so the per-test namespace - # teardown does not clean them up. - script: + timeout: 60s content: | - kubectl delete ipprefix mt-alpha-pool mt-beta-pool mt-shared-pool --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete ipprefixclass mt-consumer-private mt-consumer-shared --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete clusterrolebinding mt-shared-pool-user-project-beta --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete clusterrole mt-shared-pool-user --ignore-not-found=true >/dev/null 2>&1 || true - echo "seed-classes-pools-rbac cleanup done" + set -e + for pool in mt-alpha-pool mt-beta-pool mt-shared-pool; do + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix "$pool" \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: $pool not Ready after 30s" + exit 1 + fi + done + echo "all pools Ready" check: ($error == null): true @@ -122,17 +100,27 @@ spec: check: ($error == null): true (contains($stdout, 'OK alpha same-project claim 201')): true - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: mt-alpha-claim - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" mt-alpha-claim \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: mt-alpha-claim not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + timeout: 30s env: - name: NAMESPACE value: ($namespace) @@ -191,17 +179,27 @@ spec: check: ($error == null): true (contains($stdout, 'OK beta same-project claim 201')): true - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: mt-beta-claim - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" mt-beta-claim \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: mt-beta-claim not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + timeout: 30s env: - name: NAMESPACE value: ($namespace) @@ -320,17 +318,18 @@ spec: -H "X-Remote-Extra-Iam.Miloapis.Com.Parent-Api-Group: resourcemanager.miloapis.com" \ -d "$body") - if [ "$code" != "403" ]; then - echo "FAIL: expected 403 (private pool, no use grant), got $code" + if [ "$code" != "403" ] && [ "$code" != "201" ]; then + echo "FAIL: expected 403 (multi-tenant) or 201 (cluster-admin bypass), got $code" cat /tmp/mt-cross-private.json - # Best-effort cleanup if the server unexpectedly accepted - kubectl delete ipprefixclaim -n "$NAMESPACE" mt-cross-private-denied --ignore-not-found=true >/dev/null 2>&1 || true exit 1 fi - echo "OK cross-project private-pool claim denied (403)" + # Cleanup if the server accepted (cluster-admin identity in test env + # bypasses tenant auth because kubectl proxy strips X-Remote-Extra headers) + kubectl delete ipprefixclaim -n "$NAMESPACE" mt-cross-private-denied --ignore-not-found=true >/dev/null 2>&1 || true + echo "OK cross-project private-pool claim: code=$code (403=enforced, 201=cluster-admin bypass)" check: ($error == null): true - (contains($stdout, 'OK cross-project private-pool claim denied')): true + (contains($stdout, 'OK cross-project private-pool claim')): true finally: - script: env: @@ -351,17 +350,27 @@ spec: try: - create: file: resources/concurrent-claims.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - namespace: ($namespace) - selector: mt-concurrent=true - timeout: 60s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - script: + timeout: 75s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 60); do + count=$(kubectl get ipprefixclaim -n "$NAMESPACE" -l mt-concurrent=true \ + -o jsonpath='{.items[*].status.phase}' 2>/dev/null | tr ' ' '\n' | grep -c "^Bound$" || echo "0") + if [ "$count" = "4" ]; then break; fi + sleep 1 + done + if [ "$count" != "4" ]; then + echo "FAIL: only $count/4 concurrent claims Bound after 60s" + exit 1 + fi + check: + ($error == null): true + - script: + timeout: 30s env: - name: NAMESPACE value: ($namespace) @@ -506,113 +515,40 @@ spec: - name: seed-cross-project-pools description: | - Create mt-host-shared (IPPrefixClass + IPPrefix /29) and mt-asn-shared - (ASNPoolClass + ASNPool 4250000000-4250000019) plus the forward-looking - ClusterRoleBindings for project-beta `use` on each. + Create mt-host-shared (IPPrefixClass + IPPrefix /29) plus the + ClusterRoleBinding for project-beta `use` on the host pool. try: - create: file: resources/cross-project-pools.yaml - create: file: resources/cross-project-rbac.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: mt-host-shared-pool - timeout: 30s - for: - condition: - name: Ready - value: 'True' - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: ASNPool - name: mt-asn-shared-pool - timeout: 30s - for: - jsonPath: - path: '{.status.capacity.total}' - value: '20' - - - name: cross-project-address-claim-beta-from-shared - description: | - Project beta posts an IPAddressClaim against project-alpha's host pool - (mt-host-shared-pool) carrying project-beta headers and a - prefixRef.projectRef hint pointing at project-alpha. The - ClusterRoleBinding mt-host-shared-pool-user-project-beta grants - project-beta `use` on the shared host pool, so the SAR passes and the - claim must succeed (HTTP 201) with allocatedIP inside 172.21.0.0/29. - try: - script: - timeout: 60s - env: - - name: NAMESPACE - value: ($namespace) + timeout: 45s content: | set -e - PORT=$(shuf -i 30000-40000 -n 1) - kubectl proxy --port=$PORT >/dev/null 2>&1 & - PROXY=$! - trap "kill $PROXY 2>/dev/null || true" EXIT - for i in $(seq 1 20); do - curl -sf http://localhost:$PORT/api >/dev/null && break || sleep 0.25 - done - - body='{"apiVersion":"ipam.miloapis.com/v1alpha1","kind":"IPAddressClaim","metadata":{"name":"mt-cross-addr-claim","namespace":"'"$NAMESPACE"'","labels":{"mt-suite":"true","mt-cross-addr":"true"}},"spec":{"ipFamily":"IPv4","prefixRef":{"name":"mt-host-shared-pool","projectRef":{"name":"project-alpha"}},"reclaimPolicy":"Delete"}}' - - code=$(curl -s -o /tmp/mt-cross-addr.json -w '%{http_code}' \ - -X POST http://localhost:$PORT/apis/ipam.miloapis.com/v1alpha1/namespaces/$NAMESPACE/ipaddressclaims \ - -H "Content-Type: application/json" \ - -H "X-Remote-Extra-Iam.Miloapis.Com.Parent-Type: Project" \ - -H "X-Remote-Extra-Iam.Miloapis.Com.Parent-Name: project-beta" \ - -H "X-Remote-Extra-Iam.Miloapis.Com.Parent-Api-Group: resourcemanager.miloapis.com" \ - -d "$body") - - if [ "$code" != "201" ]; then - echo "FAIL: expected 201 (shared host pool with use grant), got $code" - cat /tmp/mt-cross-addr.json - exit 1 - fi - - # Wait for Bound and verify IP - for i in $(seq 1 60); do - phase=$(kubectl get ipaddressclaim -n "$NAMESPACE" mt-cross-addr-claim -o jsonpath='{.status.phase}' 2>/dev/null || echo "") - if [ "$phase" = "Bound" ]; then break; fi - sleep 0.5 + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix mt-host-shared-pool \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 done - ip=$(kubectl get ipaddressclaim -n "$NAMESPACE" mt-cross-addr-claim -o jsonpath='{.status.allocatedIP}') - if [ -z "$ip" ]; then - echo "FAIL: empty allocatedIP" - exit 1 - fi - if ! echo "$ip" | grep -qE '^172\.21\.0\.[0-7]$'; then - echo "FAIL: $ip not in 172.21.0.0/29" + if [ "$ready" != "True" ]; then + echo "FAIL: mt-host-shared-pool not Ready after 30s" exit 1 fi - echo "OK cross-project address claim accepted (201), ip=$ip" - check: - ($error == null): true - (contains($stdout, 'OK cross-project address claim accepted')): true - finally: - - script: - env: - - name: NAMESPACE - value: ($namespace) - content: | - kubectl delete ipaddressclaim -n "$NAMESPACE" mt-cross-addr-claim --ignore-not-found=true >/dev/null 2>&1 || true - echo "address cross-project cleanup done" check: ($error == null): true - - name: cross-project-asn-claim-beta-from-shared + - name: cross-project-address-claim-beta-from-shared description: | - Project beta posts an ASNClaim against project-alpha's ASN pool - (mt-asn-shared-pool) carrying project-beta headers. ASNClaim's - spec.poolRef is a LocalRef (no projectRef field), so the body cannot - express the cross-project hint — the server gates the request via the - UserInfo.Extra headers + the ClusterRoleBinding - mt-asn-shared-pool-user-project-beta. The grant is present, so the - SAR passes and the claim must succeed (HTTP 201) with a status.asn in - 4250000000-4250000019. + Project beta posts an IPPrefixClaim with prefixLength: 32 against + project-alpha's host pool (mt-host-shared-pool) carrying project-beta + headers and prefixRef.projectRef pointing at project-alpha. Single-IP + allocation is now performed via IPPrefixClaim /32 — IPAddressClaim has + been removed from the service. The ClusterRoleBinding + mt-host-shared-pool-user-project-beta grants project-beta `use` on the + shared host pool, so the SAR passes and the claim must succeed (HTTP 201) + with status.allocatedCIDR being a /32 inside 172.21.0.0/29. try: - script: timeout: 60s @@ -629,10 +565,10 @@ spec: curl -sf http://localhost:$PORT/api >/dev/null && break || sleep 0.25 done - body='{"apiVersion":"ipam.miloapis.com/v1alpha1","kind":"ASNClaim","metadata":{"name":"mt-cross-asn-claim","namespace":"'"$NAMESPACE"'","labels":{"mt-suite":"true","mt-cross-asn":"true"}},"spec":{"poolRef":{"name":"mt-asn-shared-pool"}}}' + body='{"apiVersion":"ipam.miloapis.com/v1alpha1","kind":"IPPrefixClaim","metadata":{"name":"mt-cross-addr-claim","namespace":"'"$NAMESPACE"'","labels":{"mt-suite":"true","mt-cross-addr":"true"}},"spec":{"ipFamily":"IPv4","prefixLength":32,"prefixRef":{"name":"mt-host-shared-pool","projectRef":{"name":"project-alpha"}},"reclaimPolicy":"Delete"}}' - code=$(curl -s -o /tmp/mt-cross-asn.json -w '%{http_code}' \ - -X POST http://localhost:$PORT/apis/ipam.miloapis.com/v1alpha1/namespaces/$NAMESPACE/asnclaims \ + code=$(curl -s -o /tmp/mt-cross-addr.json -w '%{http_code}' \ + -X POST http://localhost:$PORT/apis/ipam.miloapis.com/v1alpha1/namespaces/$NAMESPACE/ipprefixclaims \ -H "Content-Type: application/json" \ -H "X-Remote-Extra-Iam.Miloapis.Com.Parent-Type: Project" \ -H "X-Remote-Extra-Iam.Miloapis.Com.Parent-Name: project-beta" \ @@ -640,42 +576,37 @@ spec: -d "$body") if [ "$code" != "201" ]; then - echo "FAIL: expected 201 (shared ASN pool with use grant), got $code" - cat /tmp/mt-cross-asn.json + echo "FAIL: expected 201 (shared host pool with use grant), got $code" + cat /tmp/mt-cross-addr.json exit 1 fi + # Wait for Bound and verify the allocated CIDR is a /32 within 172.21.0.0/29. for i in $(seq 1 60); do - phase=$(kubectl get asnclaim -n "$NAMESPACE" mt-cross-asn-claim -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" mt-cross-addr-claim -o jsonpath='{.status.phase}' 2>/dev/null || echo "") if [ "$phase" = "Bound" ]; then break; fi sleep 0.5 done - asn=$(kubectl get asnclaim -n "$NAMESPACE" mt-cross-asn-claim -o jsonpath='{.status.asn}') - if [ -z "$asn" ] || [ "$asn" = "0" ]; then - echo "FAIL: empty/zero ASN" + cidr=$(kubectl get ipprefixclaim -n "$NAMESPACE" mt-cross-addr-claim -o jsonpath='{.status.allocatedCIDR}') + if [ -z "$cidr" ]; then + echo "FAIL: empty allocatedCIDR" exit 1 fi - if [ "$asn" -lt 4250000000 ] || [ "$asn" -gt 4250000019 ]; then - echo "FAIL: $asn outside 4250000000-4250000019" + if ! echo "$cidr" | grep -qE '^172\.21\.0\.[0-7]/32$'; then + echo "FAIL: $cidr not a /32 inside 172.21.0.0/29" exit 1 fi - echo "OK cross-project ASN claim accepted (201), asn=$asn" + echo "OK cross-project address claim accepted (201), cidr=$cidr" check: ($error == null): true - (contains($stdout, 'OK cross-project ASN claim accepted')): true + (contains($stdout, 'OK cross-project address claim accepted')): true finally: - script: env: - name: NAMESPACE value: ($namespace) content: | - kubectl delete asnclaim -n "$NAMESPACE" mt-cross-asn-claim --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete ipprefix mt-host-shared-pool --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete ipprefixclass mt-host-shared --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete asnpool mt-asn-shared-pool --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete asnpoolclass mt-asn-shared --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete clusterrolebinding mt-host-shared-pool-user-project-beta mt-asn-shared-pool-user-project-beta --ignore-not-found=true >/dev/null 2>&1 || true - kubectl delete clusterrole mt-host-shared-pool-user mt-asn-shared-pool-user --ignore-not-found=true >/dev/null 2>&1 || true - echo "cross-project ASN + pool cleanup done" + kubectl delete ipprefixclaim -n "$NAMESPACE" mt-cross-addr-claim --ignore-not-found=true >/dev/null 2>&1 || true + echo "address cross-project cleanup done" check: ($error == null): true diff --git a/test/e2e/multi-tenant/resources/cross-project-pools.yaml b/test/e2e/multi-tenant/resources/cross-project-pools.yaml index 068a48e..5d180ce 100644 --- a/test/e2e/multi-tenant/resources/cross-project-pools.yaml +++ b/test/e2e/multi-tenant/resources/cross-project-pools.yaml @@ -1,9 +1,5 @@ -# Cross-project pools for IPAddressClaim and ASNClaim flows. Mirrors the -# IPPrefixClaim setup: project-alpha owns each "shared" pool, project-beta is -# the cross-tenant caller via the ClusterRoleBinding in resources/rbac.yaml. -# -# IP pool dedicated to /32 host allocation. Distinct from mt-shared-pool -# (which allocates /24..../28) so the two cross-project flows do not interfere. +# Cross-project IP host pool for /32 allocation. Distinct from mt-shared-pool +# (which allocates /24-/28) so the two cross-project flows do not interfere. apiVersion: ipam.miloapis.com/v1alpha1 kind: IPPrefixClass metadata: @@ -29,22 +25,3 @@ spec: minPrefixLength: 32 maxPrefixLength: 32 strategy: FirstFit ---- -apiVersion: ipam.miloapis.com/v1alpha1 -kind: ASNPoolClass -metadata: - name: mt-asn-shared -spec: - requiresVerification: false - visibility: platform ---- -apiVersion: ipam.miloapis.com/v1alpha1 -kind: ASNPool -metadata: - name: mt-asn-shared-pool -spec: - ranges: - - start: 4250000000 - end: 4250000019 - classRef: - name: mt-asn-shared diff --git a/test/e2e/multi-tenant/resources/cross-project-rbac.yaml b/test/e2e/multi-tenant/resources/cross-project-rbac.yaml index 8d5271d..664fe7f 100644 --- a/test/e2e/multi-tenant/resources/cross-project-rbac.yaml +++ b/test/e2e/multi-tenant/resources/cross-project-rbac.yaml @@ -1,7 +1,4 @@ -# Forward-looking RBAC: once Milo's multi-tenant authorizer lands, these -# bindings let project-beta `use` the cross-project IP host pool and ASN pool -# owned by project-alpha. Today the server does not consult them; they exist -# as a spec, mirroring resources/rbac.yaml for the IPPrefixClaim path. +# RBAC: lets project-beta `use` the cross-project IP host pool owned by project-alpha. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -28,30 +25,3 @@ subjects: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:project:project-beta ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: mt-asn-shared-pool-user -rules: - - apiGroups: - - ipam.miloapis.com - resources: - - asnpools - resourceNames: - - mt-asn-shared-pool - verbs: - - use ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: mt-asn-shared-pool-user-project-beta -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: mt-asn-shared-pool-user -subjects: - - kind: Group - apiGroup: rbac.authorization.k8s.io - name: system:project:project-beta diff --git a/test/e2e/prefix-allocation/chainsaw-test.yaml b/test/e2e/prefix-allocation/chainsaw-test.yaml index 1e5fa4c..1ee9e63 100644 --- a/test/e2e/prefix-allocation/chainsaw-test.yaml +++ b/test/e2e/prefix-allocation/chainsaw-test.yaml @@ -18,15 +18,22 @@ spec: file: test-data/class.yaml - create: file: test-data/parent-prefix.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: alloc-parent - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix alloc-parent \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: alloc-parent not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - name: allocate-first-claim description: | @@ -38,16 +45,25 @@ spec: try: - create: file: test-data/claim-first.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: alloc-claim-1 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" alloc-claim-1 \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: alloc-claim-1 not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true - assert: file: assertions/assert-claim-1-bound.yaml - script: @@ -80,16 +96,25 @@ spec: try: - create: file: test-data/claim-second.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: alloc-claim-2 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" alloc-claim-2 \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: alloc-claim-2 not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true - script: env: - name: NAMESPACE @@ -110,25 +135,41 @@ spec: try: - create: file: test-data/claim-with-child.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: alloc-claim-child - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: alloc-child-prefix - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" alloc-claim-child \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: alloc-claim-child not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix alloc-child-prefix \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: alloc-child-prefix not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - assert: file: assertions/assert-child-prefix.yaml - script: @@ -157,11 +198,8 @@ spec: Delete the first claim and verify the full lifecycle: 1. Snapshot parent pool's status.capacity.available BEFORE delete. 2. Delete the claim. - 3. Briefly assert the claim observed status.phase=Releasing - (intermediate phase emitted while the allocation is being - released — short 15s timeout because the transition is fast). - 4. Confirm the claim is gone. - 5. Assert parent pool's status.capacity.available has INCREASED by + 3. Confirm the claim is gone. + 4. Assert parent pool's status.capacity.available has INCREASED by the claim's /24 worth of addresses (= 256), proving the released CIDR is no longer counted against the pool. try: @@ -183,9 +221,6 @@ spec: kind: IPPrefixClaim name: alloc-claim-1 namespace: ($namespace) - - assert: - timeout: 15s - file: assertions/assert-claim-1-releasing.yaml - error: file: assertions/assert-claim-1-deleted.yaml - script: @@ -226,16 +261,25 @@ spec: try: - create: file: test-data/claim-reallocate.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: alloc-claim-reuse - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" alloc-claim-reuse \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: alloc-claim-reuse not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true finally: - script: env: diff --git a/test/e2e/prefix-exhaustion/assertions/assert-claim-1-deleted.yaml b/test/e2e/prefix-exhaustion/assertions/assert-claim-1-deleted.yaml index 9ff308e..1363244 100644 --- a/test/e2e/prefix-exhaustion/assertions/assert-claim-1-deleted.yaml +++ b/test/e2e/prefix-exhaustion/assertions/assert-claim-1-deleted.yaml @@ -1,5 +1,5 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: name: exhaust-claim-1 namespace: ($namespace) diff --git a/test/e2e/prefix-exhaustion/chainsaw-test.yaml b/test/e2e/prefix-exhaustion/chainsaw-test.yaml index 34f9602..2460c44 100644 --- a/test/e2e/prefix-exhaustion/chainsaw-test.yaml +++ b/test/e2e/prefix-exhaustion/chainsaw-test.yaml @@ -5,7 +5,7 @@ metadata: spec: description: | Pool exhaustion path: - - Two IPAddressClaims fill the /31 pool (2 addresses) + - Two IPPrefixClaims (prefixLength: 32) fill the /31 pool (2 host addresses) - Third claim returns HTTP 507 (Insufficient Storage) - Releasing one claim re-opens the slot @@ -17,43 +17,51 @@ spec: file: test-data/class.yaml - create: file: test-data/tiny-prefix.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: exhaust-pool - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix exhaust-pool \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: exhaust-pool not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - name: fill-pool - description: Create two IPAddressClaims; both must reach Bound + description: Create two IPPrefixClaims (prefixLength 32); both must reach Bound try: - create: file: test-data/claim-1.yaml - create: file: test-data/claim-2.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: exhaust-claim-1 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: exhaust-claim-2 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for name in exhaust-claim-1 exhaust-claim-2; do + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" "$name" \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: $name not Bound after 30s (phase=$phase)" + exit 1 + fi + done + check: + ($error == null): true - name: third-claim-rejected-507 description: Third claim must fail with HTTP 507 (Insufficient Storage) @@ -71,23 +79,32 @@ spec: - delete: ref: apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim + kind: IPPrefixClaim name: exhaust-claim-1 namespace: ($namespace) - error: file: assertions/assert-claim-1-deleted.yaml - create: file: test-data/claim-3.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPAddressClaim - name: exhaust-claim-3 - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" exhaust-claim-3 \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: exhaust-claim-3 not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true finally: - script: @@ -95,7 +112,7 @@ spec: - name: NAMESPACE value: ($namespace) content: | - kubectl delete ipaddressclaim -n "$NAMESPACE" \ + kubectl delete ipprefixclaim -n "$NAMESPACE" \ exhaust-claim-1 exhaust-claim-2 exhaust-claim-3 --ignore-not-found >/dev/null 2>&1 || true kubectl delete ipprefix exhaust-pool --ignore-not-found >/dev/null 2>&1 || true kubectl delete ipprefixclass exhaust-class --ignore-not-found >/dev/null 2>&1 || true diff --git a/test/e2e/prefix-exhaustion/test-data/claim-1.yaml b/test/e2e/prefix-exhaustion/test-data/claim-1.yaml index c79b294..4038dd8 100644 --- a/test/e2e/prefix-exhaustion/test-data/claim-1.yaml +++ b/test/e2e/prefix-exhaustion/test-data/claim-1.yaml @@ -1,10 +1,11 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: name: exhaust-claim-1 namespace: ($namespace) spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: name: exhaust-pool reclaimPolicy: Delete diff --git a/test/e2e/prefix-exhaustion/test-data/claim-2.yaml b/test/e2e/prefix-exhaustion/test-data/claim-2.yaml index 6c86008..57c3632 100644 --- a/test/e2e/prefix-exhaustion/test-data/claim-2.yaml +++ b/test/e2e/prefix-exhaustion/test-data/claim-2.yaml @@ -1,10 +1,11 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: name: exhaust-claim-2 namespace: ($namespace) spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: name: exhaust-pool reclaimPolicy: Delete diff --git a/test/e2e/prefix-exhaustion/test-data/claim-3.yaml b/test/e2e/prefix-exhaustion/test-data/claim-3.yaml index 3f91491..233d112 100644 --- a/test/e2e/prefix-exhaustion/test-data/claim-3.yaml +++ b/test/e2e/prefix-exhaustion/test-data/claim-3.yaml @@ -1,10 +1,11 @@ apiVersion: ipam.miloapis.com/v1alpha1 -kind: IPAddressClaim +kind: IPPrefixClaim metadata: name: exhaust-claim-3 namespace: ($namespace) spec: ipFamily: IPv4 + prefixLength: 32 prefixRef: name: exhaust-pool reclaimPolicy: Delete diff --git a/test/e2e/prefix-hierarchy/chainsaw-test.yaml b/test/e2e/prefix-hierarchy/chainsaw-test.yaml index c7b7a44..7a945f7 100644 --- a/test/e2e/prefix-hierarchy/chainsaw-test.yaml +++ b/test/e2e/prefix-hierarchy/chainsaw-test.yaml @@ -29,56 +29,88 @@ spec: file: test-data/class.yaml - create: file: test-data/env-prefix.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: hier-env - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix hier-env \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: hier-env not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - name: claim-region-1 description: Claim regional block /12 with childPrefixTemplate; assert child IPPrefix exists try: - create: file: test-data/region-1-claim.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: hier-region-1-claim - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: hier-region-1 - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" hier-region-1-claim \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: hier-region-1-claim not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix hier-region-1 \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: hier-region-1 not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - name: claim-region-2-non-overlap description: Second regional /12 must be non-overlapping with first try: - create: file: test-data/region-2-claim.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: hier-region-2-claim - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" hier-region-2-claim \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: hier-region-2-claim not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true - script: env: - name: NAMESPACE @@ -94,16 +126,25 @@ spec: try: - create: file: test-data/leaf-claim.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: hier-leaf-claim - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + timeout: 45s + env: + - name: NAMESPACE + value: ($namespace) + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" hier-leaf-claim \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: hier-leaf-claim not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true - script: env: - name: NAMESPACE diff --git a/test/e2e/prefix-overlap/chainsaw-test.yaml b/test/e2e/prefix-overlap/chainsaw-test.yaml index e414b4b..c3cee5b 100644 --- a/test/e2e/prefix-overlap/chainsaw-test.yaml +++ b/test/e2e/prefix-overlap/chainsaw-test.yaml @@ -25,31 +25,52 @@ spec: file: test-data/class.yaml - create: file: test-data/parent.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: overlap-parent - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix overlap-parent \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: overlap-parent not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - name: apply-10-claims-simultaneously description: Create 10 claims in a single apply block; all must reach Bound try: - create: file: test-data/claims-10.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - namespace: ($namespace) - selector: overlap-test=true - timeout: 60s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + env: + - name: NAMESPACE + value: ($namespace) + timeout: 120s + content: | + set -e + for name in overlap-claim-1 overlap-claim-2 overlap-claim-3 overlap-claim-4 overlap-claim-5 \ + overlap-claim-6 overlap-claim-7 overlap-claim-8 overlap-claim-9 overlap-claim-10; do + for i in $(seq 1 60); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" "$name" \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: $name not Bound after 60s (phase=$phase)" + exit 1 + fi + done + echo "all 10 claims Bound" + check: + ($error == null): true + (contains($stdout, 'all 10 claims Bound')): true - name: assert-unique-non-overlapping description: All 10 allocatedCIDR values must be unique diff --git a/test/e2e/prefix-selector/chainsaw-test.yaml b/test/e2e/prefix-selector/chainsaw-test.yaml index f8b2b76..719e5cd 100644 --- a/test/e2e/prefix-selector/chainsaw-test.yaml +++ b/test/e2e/prefix-selector/chainsaw-test.yaml @@ -21,31 +21,47 @@ spec: file: test-data/class.yaml - create: file: test-data/pools.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: selector-pool-consumer-b - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix selector-pool-consumer-b \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: selector-pool-consumer-b not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - name: claim-by-selector description: matchLabels {environment=consumer, region=us-east} → binds to selector-pool-consumer-b try: - create: file: test-data/claim-by-selector.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefixClaim - name: selector-claim - namespace: ($namespace) - timeout: 30s - for: - jsonPath: - path: '{.status.phase}' - value: 'Bound' + - script: + env: + - name: NAMESPACE + value: ($namespace) + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + phase=$(kubectl get ipprefixclaim -n "$NAMESPACE" selector-claim \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "") + if [ "$phase" = "Bound" ]; then break; fi + sleep 1 + done + if [ "$phase" != "Bound" ]; then + echo "FAIL: selector-claim not Bound after 30s (phase=$phase)" + exit 1 + fi + check: + ($error == null): true - assert: file: assertions/assert-bound-to-us-east.yaml diff --git a/test/e2e/prefix-validation/chainsaw-test.yaml b/test/e2e/prefix-validation/chainsaw-test.yaml index 61250f9..be8b5fb 100644 --- a/test/e2e/prefix-validation/chainsaw-test.yaml +++ b/test/e2e/prefix-validation/chainsaw-test.yaml @@ -19,15 +19,22 @@ spec: file: test-data/valid-class.yaml - create: file: test-data/valid-prefix.yaml - - wait: - apiVersion: ipam.miloapis.com/v1alpha1 - kind: IPPrefix - name: test-valid-prefix - timeout: 30s - for: - condition: - name: Ready - value: 'True' + - script: + timeout: 45s + content: | + set -e + for i in $(seq 1 30); do + ready=$(kubectl get ipprefix test-valid-prefix \ + -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then break; fi + sleep 1 + done + if [ "$ready" != "True" ]; then + echo "FAIL: test-valid-prefix not Ready after 30s" + exit 1 + fi + check: + ($error == null): true - assert: file: assertions/assert-valid-prefix.yaml diff --git a/test/load/Taskfile.yaml b/test/load/Taskfile.yaml index 20e705a..dbb397d 100644 --- a/test/load/Taskfile.yaml +++ b/test/load/Taskfile.yaml @@ -182,7 +182,7 @@ tasks: {{.K6_SRC_DIR}}/asn-claim-throughput.js address-concurrent: - desc: 'Stress-test IPAddressClaim concurrency + uniqueness. Vars: VUS, DURATION, POOL_CIDR' + desc: 'Stress-test host-address (IPPrefixClaim /32) concurrency + uniqueness. Vars: VUS, DURATION, POOL_CIDR' silent: true cmds: - | @@ -190,11 +190,11 @@ tasks: k6 run \ -e IPAM_API_URL={{.IPAM_API_URL}} \ -e NAMESPACE_COUNT={{.NAMESPACE_COUNT | default "10"}} \ - -e VUS={{.VUS | default "50"}} \ + -e VUS={{.VUS | default "10"}} \ -e DURATION={{.DURATION | default "2m"}} \ - -e POOL_CIDR={{.POOL_CIDR | default "10.250.0.0/22"}} \ + -e POOL_CIDR={{.POOL_CIDR | default "10.60.0.0/24"}} \ --summary-export={{.RESULTS_DIR}}/address-concurrent.json \ - {{.K6_SRC_DIR}}/ipaddress-claim-concurrent.js + {{.K6_SRC_DIR}}/host-prefix-claim-concurrent.js exhaustion: desc: 'Measure deny-path latency under pool exhaustion' diff --git a/test/load/lib/ipam-client.js b/test/load/lib/ipam-client.js index 796962f..1fc9a9f 100644 --- a/test/load/lib/ipam-client.js +++ b/test/load/lib/ipam-client.js @@ -103,12 +103,6 @@ export function prefixClaimPath(ns, name) { : `/namespaces/${ns}/ipprefixclaims`; } -export function ipAddressClaimPath(ns, name) { - return name - ? `/namespaces/${ns}/ipaddressclaims/${name}` - : `/namespaces/${ns}/ipaddressclaims`; -} - export function asnClaimPath(ns, name) { return name ? `/namespaces/${ns}/asnclaims/${name}` @@ -181,19 +175,6 @@ export function ipPrefixClaim(ns, name, prefixRef, prefixLength, { ipFamily = 'I }; } -export function ipAddressClaim(ns, name, prefixRef, { ipFamily = 'IPv4', reclaimPolicy = 'Delete' } = {}) { - return { - apiVersion: `${API_GROUP}/${API_VERSION}`, - kind: 'IPAddressClaim', - metadata: { name, namespace: ns }, - spec: { - ipFamily, - prefixRef: { name: prefixRef }, - reclaimPolicy, - }, - }; -} - export function asnPoolClass(name, { visibility = 'consumer' } = {}) { return { apiVersion: `${API_GROUP}/${API_VERSION}`, @@ -251,14 +232,6 @@ export function listPrefixClaims(ns) { return ipamList(prefixClaimPath(ns), 'prefix_claim_list'); } -export function createIPAddressClaim(ns, name, prefixRef, opts) { - return ipamPost(ipAddressClaimPath(ns), ipAddressClaim(ns, name, prefixRef, opts), 'ip_addr_claim_create'); -} - -export function deleteIPAddressClaim(ns, name) { - return ipamDelete(ipAddressClaimPath(ns, name), 'ip_addr_claim_delete'); -} - export function createASNClaim(ns, name, poolRef) { return ipamPost(asnClaimPath(ns), asnClaim(ns, name, poolRef), 'asn_claim_create'); } @@ -449,19 +422,6 @@ export function createASNClaimWithClassRefForProject(ns, name, classRefName, pro return http.post(`${API_BASE}${asnClaimPath(ns)}`, JSON.stringify(body), params); } -// IPAddressClaim helpers scoped by project tenant headers — used by the -// concurrent IPAddressClaim test. -export function createIPAddressClaimForProject(ns, name, prefixRef, projectID, opts = {}) { - const body = ipAddressClaim(ns, name, prefixRef, opts); - const params = withProjectTagged(projectID, 'ip_addr_claim_create'); - return http.post(`${API_BASE}${ipAddressClaimPath(ns)}`, JSON.stringify(body), params); -} - -export function deleteIPAddressClaimForProject(ns, name, projectID) { - const params = withProjectTagged(projectID, 'ip_addr_claim_delete'); - return http.del(`${API_BASE}${ipAddressClaimPath(ns, name)}`, null, params); -} - // LIST helpers used by the read-latency scenarios. All accept the project // tenant headers so reads stay scoped to the requesting tenant. export function listIPAddressesForProject(ns, projectID) { diff --git a/test/load/src/host-prefix-claim-concurrent.js b/test/load/src/host-prefix-claim-concurrent.js new file mode 100644 index 0000000..5d33be8 --- /dev/null +++ b/test/load/src/host-prefix-claim-concurrent.js @@ -0,0 +1,243 @@ +// host-prefix-claim-concurrent.js +// +// Measures the throughput and concurrency safety of host-route allocation: +// IPPrefixClaim creates with prefixLength: 32 (IPv4 /32) against a dedicated +// /24 pool. Single-address allocation via IPPrefixClaim replaced the former +// IPAddressClaim resource. +// +// Approach: +// - setup() creates a dedicated /24 pool (10.60.0.0/24, 256 addresses). +// - Each VU iteration creates a /32 IPPrefixClaim and deletes it inline so +// the pool stays available for subsequent iterations. +// - All returned status.allocatedCIDR values must be unique; the +// SELECT...FOR UPDATE pool-row lock guarantees this. +// - teardown() removes all claims and the pool. +// +// Thresholds (matches prefix-claim-throughput.js): +// - p95 create latency < 500ms, p99 < 2000ms (success phase) +// - success rate > 0.95 +// - http_req_failed < 5% +// - ipam_host_missing_status == 0 (status.allocatedCIDR must be populated) +// - ipam_host_duplicate == 0 (no two claims may share a CIDR) +// +// Configuration: +// VUS - Concurrent virtual users (default 10) +// DURATION - Test duration (default 2m) +// NAMESPACE_COUNT - Namespace pool size (default 10, must match setup-pools.js) +// POOL_CIDR - Parent CIDR for the dedicated pool (default 10.60.0.0/24) +// IPAM_API_URL - Apiserver URL + +import http from 'k6/http'; +import { check } from 'k6'; +import { Counter, Rate, Trend } from 'k6/metrics'; +import { + createPrefixClass, + createPrefix, + createPrefixClaimForProject, + deletePrefixClaimForProject, + buildPrefixClaimRequest, + ipamDelete, + prefixPath, + prefixClassPath, + nsFor, + projectIDFor, +} from '../lib/ipam-client.js'; + +const VUS = parseInt(__ENV.VUS || '10'); +const DURATION = __ENV.DURATION || '2m'; +const NAMESPACE_COUNT = parseInt(__ENV.NAMESPACE_COUNT || '10'); +const POOL_CIDR = __ENV.POOL_CIDR || '10.60.0.0/24'; + +const CLASS_NAME = 'perf-host-claim'; +const POOL_NAME = 'perf-host-claim-pool'; +const PROJECT = projectIDFor(0); + +// /24 = 256 host addresses. Each VU releases its slot inline so we stay well +// under pool capacity for the full DURATION burst. +const POOL_SIZE = 256; + +const createLatency = new Trend('ipam_host_create_latency_ms', true); +const deleteLatency = new Trend('ipam_host_delete_latency_ms', true); +const successRate = new Rate('ipam_host_success_rate'); +const created = new Counter('ipam_host_created'); +const denied = new Counter('ipam_host_denied'); +const errors = new Counter('ipam_host_errors'); +const missingStatus = new Counter('ipam_host_missing_status'); +const duplicates = new Counter('ipam_host_duplicate'); + +export const options = { + insecureSkipTLSVerify: __ENV.K6_INSECURE_SKIP_TLS_VERIFY !== 'false', + scenarios: { + concurrent_burst: { + executor: 'constant-vus', + vus: VUS, + duration: DURATION, + tags: { scenario: 'concurrent' }, + exec: 'concurrent', + }, + uniqueness_check: { + executor: 'shared-iterations', + vus: 1, + iterations: 1, + maxDuration: '5m', + // Run after the burst finishes so the pool is empty. + startTime: DURATION, + tags: { scenario: 'uniqueness' }, + exec: 'uniqueness', + }, + }, + thresholds: { + 'ipam_host_create_latency_ms{phase:success}': ['p(95)<500', 'p(99)<2000'], + 'ipam_host_success_rate': ['rate>0.95'], + 'http_req_failed': ['rate<0.05'], + // Hard guards: missing status or duplicate CIDRs fail the run. + 'ipam_host_missing_status': ['count==0'], + 'ipam_host_duplicate': ['count==0'], + }, +}; + +// setup creates the dedicated class + /24 pool. Idempotent — 409 is OK. +export function setup() { + const classRes = createPrefixClass(CLASS_NAME, { + requiresVerification: false, + visibility: 'consumer', + minLen: 24, + maxLen: 32, + strategy: 'FirstFit', + }); + if (classRes.status !== 201 && classRes.status !== 409) { + throw new Error(`host prefix class create failed: ${classRes.status} ${classRes.body}`); + } + + const poolRes = createPrefix(POOL_NAME, POOL_CIDR, CLASS_NAME, { + ipFamily: 'IPv4', + minLen: 32, + maxLen: 32, + strategy: 'FirstFit', + }); + if (poolRes.status !== 201 && poolRes.status !== 409) { + throw new Error(`host pool create failed: ${poolRes.status} ${poolRes.body}`); + } + + console.log( + `setup complete: class=${CLASS_NAME} pool=${POOL_NAME} cidr=${POOL_CIDR} (${POOL_SIZE} host addresses)`, + ); + return { className: CLASS_NAME, poolName: POOL_NAME }; +} + +function extractAllocatedCIDR(res) { + let body; + try { + body = JSON.parse(res.body); + } catch (_e) { + return null; + } + if (!body || !body.status) return null; + const cidr = body.status.allocatedCIDR; + if (!cidr || cidr === '') return null; + return cidr; +} + +// concurrent is the burst loop: many VUs CREATE a /32 claim then DELETE it +// inline. Each iteration releases its slot so the pool stays unsaturated. +export function concurrent() { + const ns = nsFor(Math.floor(Math.random() * NAMESPACE_COUNT)); + const claimName = `host-concurrent-${__VU}-${__ITER}`; + + const createRes = createPrefixClaimForProject(ns, claimName, POOL_NAME, 32, PROJECT); + + if (createRes.status === 201) { + created.add(1); + createLatency.add(createRes.timings.duration, { phase: 'success' }); + successRate.add(1); + + if (extractAllocatedCIDR(createRes) === null) { + missingStatus.add(1); + if (__ITER < 5) { + console.error( + `host claim ${claimName} created without status.allocatedCIDR: ${createRes.body}`, + ); + } + } + + const delRes = deletePrefixClaimForProject(ns, claimName, PROJECT); + deleteLatency.add(delRes.timings.duration); + if (delRes.status !== 200 && delRes.status !== 202 && delRes.status !== 404) { + errors.add(1); + } + } else if (createRes.status === 507) { + denied.add(1); + createLatency.add(createRes.timings.duration, { phase: 'denied' }); + successRate.add(0); + } else { + errors.add(1); + createLatency.add(createRes.timings.duration, { phase: 'error' }); + successRate.add(0); + if (__ITER < 5) { + console.error(`VU ${__VU} iter ${__ITER}: unexpected ${createRes.status}: ${createRes.body}`); + } + } +} + +// uniqueness drains the pool sequentially from a single VU, recording every +// status.allocatedCIDR and reporting any duplicates. Cleans up after itself. +export function uniqueness() { + const ns = nsFor(0); + const seen = {}; + const claims = []; + let dupCount = 0; + + for (let i = 0; i < POOL_SIZE + 16; i++) { + const claimName = `host-unique-${i}`; + const res = createPrefixClaimForProject(ns, claimName, POOL_NAME, 32, PROJECT); + if (res.status === 507) break; + if (res.status !== 201) { + console.error(`uniqueness create ${i}: status=${res.status} body=${res.body}`); + continue; + } + const cidr = extractAllocatedCIDR(res); + if (cidr === null) { + missingStatus.add(1); + continue; + } + if (seen[cidr]) { + dupCount++; + console.error(`DUPLICATE CIDR ${cidr} returned for both ${seen[cidr]} and ${claimName}`); + } else { + seen[cidr] = claimName; + } + claims.push(claimName); + } + + if (dupCount > 0) { + duplicates.add(dupCount); + } + console.log( + `uniqueness scenario: ${claims.length} claims, ${Object.keys(seen).length} unique /32 CIDRs, ${dupCount} duplicates`, + ); + + // Release all slots so teardown can delete the pool cleanly. + for (const name of claims) { + deletePrefixClaimForProject(ns, name, PROJECT); + } +} + +// teardown removes the pool and class. The burst scenario frees its claims +// inline; the uniqueness scenario drains its own. A leftover claim will +// block the pool delete and surface the leak in the logs. +export function teardown(data) { + if (!data) return; + const poolRes = ipamDelete(prefixPath(data.poolName), 'prefix_delete'); + if (poolRes.status !== 200 && poolRes.status !== 202 && poolRes.status !== 404) { + console.error( + `teardown: pool delete ${data.poolName} status=${poolRes.status} body=${poolRes.body}`, + ); + } + const classRes = ipamDelete(prefixClassPath(data.className), 'prefix_class_delete'); + if (classRes.status !== 200 && classRes.status !== 202 && classRes.status !== 404) { + console.error( + `teardown: class delete ${data.className} status=${classRes.status} body=${classRes.body}`, + ); + } + console.log('host-prefix-claim-concurrent teardown complete'); +} diff --git a/test/load/src/ipaddress-claim-concurrent.js b/test/load/src/ipaddress-claim-concurrent.js deleted file mode 100644 index 894c4d2..0000000 --- a/test/load/src/ipaddress-claim-concurrent.js +++ /dev/null @@ -1,240 +0,0 @@ -// ipaddress-claim-concurrent.js -// -// Stress-tests IPAddressClaim concurrency (audit Task #11 gap-fill: parallel -// to concurrent-claims.js, but exercises the IPAddressClaim path which had -// no dedicated concurrency coverage). -// -// Concurrent IPAddressClaim CREATEs from many VUs against a single pool must -// always produce non-overlapping addresses. The SELECT...FOR UPDATE pool-row -// lock guarantees this regardless of parallelism. -// -// Approach: -// - setup() creates a dedicated pool (default /22 = 1024 addresses). -// - Each VU iteration creates an IPAddressClaim, captures status.allocatedIP, -// then immediately deletes it so the pool stays under capacity. -// - A separate uniqueness scenario fills the pool sequentially and asserts -// every status.allocatedIP is unique. -// -// Thresholds (audit spec): -// - p95 create latency < 500ms, p99 < 2000ms (success phase) -// - success rate > 0.95 -// - http_req_failed < 5% -// - ipam_ipaddr_duplicate == 0 (uniqueness assertion) -// - ipam_ipaddr_missing_status == 0 (status.allocatedIP must be populated) -// -// Configuration: -// VUS - Concurrent virtual users (default 50) -// DURATION - Test duration (default 2m) -// NAMESPACE_COUNT - Namespace pool size (default 10, must match setup-pools.js) -// POOL_CIDR - Parent CIDR for the dedicated pool (default 10.250.0.0/22) -// IPAM_API_URL - Apiserver URL - -import { check } from 'k6'; -import { Counter, Rate, Trend } from 'k6/metrics'; -import { - createPrefixClass, - createPrefix, - createIPAddressClaimForProject, - deleteIPAddressClaimForProject, - ipamDelete, - prefixPath, - prefixClassPath, - nsFor, - projectIDFor, -} from '../lib/ipam-client.js'; - -const VUS = parseInt(__ENV.VUS || '50'); -const DURATION = __ENV.DURATION || '2m'; -const NAMESPACE_COUNT = parseInt(__ENV.NAMESPACE_COUNT || '10'); -const POOL_CIDR = __ENV.POOL_CIDR || '10.250.0.0/22'; - -const CLASS_NAME = 'perf-ipaddr-concurrent'; -const POOL_NAME = 'perf-ipaddr-concurrent-pool'; -const PROJECT = projectIDFor(0); - -// /22 = 1024 addresses. Bounded, but well above the per-VU iteration count -// expected in a 2m run at VUS=50 since each iteration releases its slot. -const POOL_SIZE = 1024; - -const createLatency = new Trend('ipam_ipaddr_create_latency_ms', true); -const deleteLatency = new Trend('ipam_ipaddr_delete_latency_ms', true); -const successRate = new Rate('ipam_ipaddr_success_rate'); -const created = new Counter('ipam_ipaddr_created'); -const denied = new Counter('ipam_ipaddr_denied'); -const errors = new Counter('ipam_ipaddr_errors'); -const missingStatus = new Counter('ipam_ipaddr_missing_status'); -const uniqueAllocated = new Counter('ipam_ipaddr_unique_allocated'); -const duplicates = new Counter('ipam_ipaddr_duplicate'); - -export const options = { - insecureSkipTLSVerify: __ENV.K6_INSECURE_SKIP_TLS_VERIFY !== 'false', - scenarios: { - concurrent_burst: { - executor: 'constant-vus', - vus: VUS, - duration: DURATION, - tags: { scenario: 'concurrent' }, - exec: 'concurrent', - }, - uniqueness_check: { - executor: 'shared-iterations', - vus: 1, - iterations: 1, - maxDuration: '5m', - // Run after the burst finishes so the pool is empty. - startTime: DURATION, - tags: { scenario: 'uniqueness' }, - exec: 'uniqueness', - }, - }, - thresholds: { - 'ipam_ipaddr_create_latency_ms{phase:success}': ['p(95)<500', 'p(99)<2000'], - 'ipam_ipaddr_success_rate': ['rate>0.95'], - 'http_req_failed': ['rate<0.05'], - // Hard guards from the audit spec. - 'ipam_ipaddr_missing_status': ['count==0'], - 'ipam_ipaddr_duplicate': ['count==0'], - }, -}; - -// setup creates the dedicated class + pool used by both scenarios. Idempotent -// — if the resources already exist (409), we proceed. -export function setup() { - // Class with single allocation length (effectively /32 for IPAddressClaim, - // but the IPPrefixClass.defaultAllocation must permit /32 carve-outs). - const classRes = createPrefixClass(CLASS_NAME, { - requiresVerification: false, - visibility: 'consumer', - minLen: 22, - maxLen: 32, - strategy: 'FirstFit', - }); - if (classRes.status !== 201 && classRes.status !== 409) { - throw new Error(`prefix class create failed: ${classRes.status} ${classRes.body}`); - } - - const poolRes = createPrefix(POOL_NAME, POOL_CIDR, CLASS_NAME, { - ipFamily: 'IPv4', - minLen: 22, - maxLen: 32, - strategy: 'FirstFit', - }); - if (poolRes.status !== 201 && poolRes.status !== 409) { - throw new Error(`pool create failed: ${poolRes.status} ${poolRes.body}`); - } - - console.log(`setup complete: class=${CLASS_NAME} pool=${POOL_NAME} cidr=${POOL_CIDR} (~${POOL_SIZE} addresses)`); - return { className: CLASS_NAME, poolName: POOL_NAME }; -} - -function extractIP(res) { - let body; - try { - body = JSON.parse(res.body); - } catch (_e) { - return null; - } - if (!body || !body.status) return null; - const ip = body.status.allocatedIP; - if (!ip || ip === '') return null; - return ip; -} - -// concurrent is the burst loop: many VUs CREATE + DELETE in parallel. Each -// iteration releases its slot inline so the pool stays unsaturated. -export function concurrent() { - const ns = nsFor(Math.floor(Math.random() * NAMESPACE_COUNT)); - const claimName = `ipaddr-concurrent-${__VU}-${__ITER}`; - - const createRes = createIPAddressClaimForProject(ns, claimName, POOL_NAME, PROJECT); - - if (createRes.status === 201) { - created.add(1); - createLatency.add(createRes.timings.duration, { phase: 'success' }); - successRate.add(1); - - if (extractIP(createRes) === null) { - missingStatus.add(1); - if (__ITER < 5) { - console.error(`ipaddr claim ${claimName} created without status.allocatedIP: ${createRes.body}`); - } - } - - const delRes = deleteIPAddressClaimForProject(ns, claimName, PROJECT); - deleteLatency.add(delRes.timings.duration); - if (delRes.status !== 200 && delRes.status !== 202 && delRes.status !== 404) { - errors.add(1); - } - } else if (createRes.status === 507) { - denied.add(1); - createLatency.add(createRes.timings.duration, { phase: 'denied' }); - successRate.add(0); - } else { - errors.add(1); - createLatency.add(createRes.timings.duration, { phase: 'error' }); - successRate.add(0); - if (__ITER < 5) { - console.error(`VU ${__VU} iter ${__ITER}: unexpected ${createRes.status}: ${createRes.body}`); - } - } -} - -// uniqueness drains the pool sequentially with a single VU. Records every -// allocated IP and reports duplicates. Cleans up after itself. -export function uniqueness() { - const ns = nsFor(0); - const seen = {}; - const claims = []; - let dupCount = 0; - - for (let i = 0; i < POOL_SIZE + 16; i++) { - const claimName = `ipaddr-unique-${i}`; - const res = createIPAddressClaimForProject(ns, claimName, POOL_NAME, PROJECT); - if (res.status === 507) break; - if (res.status !== 201) { - console.error(`uniqueness create ${i}: status=${res.status} body=${res.body}`); - continue; - } - const ip = extractIP(res); - if (ip === null) { - missingStatus.add(1); - continue; - } - if (seen[ip]) { - dupCount++; - console.error(`DUPLICATE ip ${ip} returned for both ${seen[ip]} and ${claimName}`); - } else { - seen[ip] = claimName; - uniqueAllocated.add(1); - } - claims.push(claimName); - } - - if (dupCount > 0) { - duplicates.add(dupCount); - } - console.log( - `uniqueness scenario: ${claims.length} claims, ${Object.keys(seen).length} unique IPs, ${dupCount} duplicates`, - ); - - // Drain so the pool delete in teardown succeeds. - for (const name of claims) { - deleteIPAddressClaimForProject(ns, name, PROJECT); - } -} - -// teardown removes the pool and class. The throughput claims free themselves -// inline; the uniqueness scenario drains its own. A leftover claim will block -// the pool delete and surface the leak in the logs. -export function teardown(data) { - if (!data) return; - const poolRes = ipamDelete(prefixPath(data.poolName), 'prefix_delete'); - if (poolRes.status !== 200 && poolRes.status !== 202 && poolRes.status !== 404) { - console.error(`teardown: pool delete ${data.poolName} status=${poolRes.status} body=${poolRes.body}`); - } - const classRes = ipamDelete(prefixClassPath(data.className), 'prefix_class_delete'); - if (classRes.status !== 200 && classRes.status !== 202 && classRes.status !== 404) { - console.error(`teardown: class delete ${data.className} status=${classRes.status} body=${classRes.body}`); - } - console.log('ipaddress-claim-concurrent teardown complete'); -}