This document defines manual acceptance tests for the legacy API migration:
- from
k8gb.absa.oss/v1beta1tok8gb.io/v1beta1 - including embedded Ingress migration to
resourceRef - including ownerReference handoff to prevent accidental Ingress garbage collection
These checks are intentionally not automated. They are meant for:
- Pre-merge confidence on the feature branch
- Post-merge upgrade validation before or during release rollout
The migration behavior validated here:
- Canonical GSLB is created from legacy GSLB (same name/namespace).
- Legacy GSLB is labeled
k8gb.io/migrated-to-k8gb-io=true. - Embedded legacy ingress is migrated to
spec.resourceRefand detached from embeddedspec.ingress. - For embedded legacy mode, legacy GSLB ownerReference is removed from the Ingress.
- Existing canonical GSLB is not overwritten.
- Already migrated legacy objects are ignored.
- Legacy deletion path removes migration finalizer after cleanup.
- Embedded migration does not fail if the Ingress is missing.
- OwnerReference cleanup also runs for already-migrated embedded legacy objects.
- Finalizer cleanup does not block deletion when embedded Ingress is missing.
For controlled migration mode, cases that expect canonical object creation require explicit trigger label:
k8gb.io/migration-requested=true
- Kubernetes cluster with k8gb installed from the target branch/release candidate
- Legacy CRD installed (
gslbs.k8gb.absa.oss) kubectlconfigured for the test clusterjqrecommended for easier inspection
Use this short flow when you just want to validate how migration is driven in practice.
K8GB_LOCAL_VERSION=test make deploy-full-local-setupLegacy demo objects are created in test-gslb, including:
legacy-ref-non-migrated-failoverandlegacy-service-runtime-demo(intentionally non-requested)legacy-ref-demo,legacy-embedded-demo, andlegacy-service-migration-demo(requested)
The Service examples reference service/legacy-service-demo, which reuses the existing multiservice-blue workload. They validate that the required k8gb.io/hostname annotation is available during legacy runtime reconciliation and is retained on the migrated canonical object.
kubectl get gslb.k8gb.absa.oss -n test-gslb legacy-ref-non-migrated-failover
# Expected to fail with NotFound before migration request
kubectl get gslb.k8gb.io -n test-gslb legacy-ref-non-migrated-failover || true
kubectl get events -n test-gslb --sort-by=.lastTimestamp | grep -E 'LegacyDeprecated|legacy-ref-non-migrated-failover'kubectl label gslb.k8gb.absa.oss -n test-gslb legacy-ref-non-migrated-failover \
k8gb.io/migration-requested=true --overwritekubectl wait -n test-gslb \
--for=jsonpath='{.metadata.labels.k8gb\.io/migrated-to-k8gb-io}'=true \
gslb.k8gb.absa.oss/legacy-ref-non-migrated-failover \
--timeout=90s
kubectl get gslb.k8gb.io -n test-gslb legacy-ref-non-migrated-failover -o yaml
kubectl get events -n test-gslb --sort-by=.lastTimestamp | grep -E 'LegacyMigrated|legacy-ref-non-migrated-failover'Use the same label operation to migrate any selected legacy object:
kubectl label gslb.k8gb.absa.oss -n <ns> <name> k8gb.io/migration-requested=true --overwriteUse a dedicated namespace:
kubectl create ns migration-e2e || trueMigration trigger helper (use in cases that expect conversion):
kubectl label gslb.k8gb.absa.oss -n migration-e2e <name> k8gb.io/migration-requested=true --overwriteCases requiring this trigger: 1, 2, 3, 4, 6, and 8.
Apply legacy referenced GSLB:
kubectl apply -f - <<'EOF_CASE1'
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: legacy-ref
namespace: migration-e2e
spec:
ingressClassName: nginx
rules:
- host: legacy-ref.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
---
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: legacy-ref
namespace: migration-e2e
spec:
resourceRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: legacy-ref
strategy:
type: roundRobin
EOF_CASE1Request migration:
kubectl label gslb.k8gb.absa.oss -n migration-e2e legacy-ref k8gb.io/migration-requested=true --overwriteValidate:
kubectl get gslb.k8gb.io -n migration-e2e legacy-ref
kubectl get gslb.k8gb.absa.oss -n migration-e2e legacy-ref -o jsonpath='{.metadata.labels.k8gb\.io/migrated-to-k8gb-io}'Expected:
- Canonical object exists.
- Legacy label equals
true.
Apply embedded legacy GSLB:
kubectl apply -f - <<'EOF_CASE2'
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: legacy-embedded
namespace: migration-e2e
spec:
ingress:
ingressClassName: nginx
rules:
- host: legacy-embedded.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
strategy:
type: roundRobin
EOF_CASE2Request migration:
kubectl label gslb.k8gb.absa.oss -n migration-e2e legacy-embedded k8gb.io/migration-requested=true --overwriteValidate canonical spec:
kubectl get gslb.k8gb.io -n migration-e2e legacy-embedded -o yamlExpected in canonical object:
spec.resourceRef.apiVersion = networking.k8s.io/v1spec.resourceRef.kind = Ingressspec.resourceRef.name = legacy-embeddedspec.ingressis empty
Apply canonical first, then legacy with same name:
kubectl apply -f - <<'EOF_CASE3'
apiVersion: k8gb.io/v1beta1
kind: Gslb
metadata:
name: existing-canonical
namespace: migration-e2e
spec:
strategy:
type: failover
primaryGeoTag: eu
resourceRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: existing-canonical
---
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: existing-canonical
namespace: migration-e2e
spec:
strategy:
type: roundRobin
resourceRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: existing-canonical
EOF_CASE3Request migration:
kubectl label gslb.k8gb.absa.oss -n migration-e2e existing-canonical k8gb.io/migration-requested=true --overwriteValidate:
kubectl get gslb.k8gb.io -n migration-e2e existing-canonical -o jsonpath='{.spec.strategy.type}'
kubectl get gslb.k8gb.absa.oss -n migration-e2e existing-canonical -o jsonpath='{.metadata.labels.k8gb\.io/migrated-to-k8gb-io}'Expected:
- Canonical strategy remains
failover(unchanged). - Legacy label equals
true.
For an embedded legacy object, verify Ingress survives legacy deletion.
Request migration first:
kubectl label gslb.k8gb.absa.oss -n migration-e2e legacy-embedded k8gb.io/migration-requested=true --overwriteCapture ownerRefs before deletion:
kubectl get ingress -n migration-e2e legacy-embedded -o json | jq '.metadata.ownerReferences'
kubectl get gslb.k8gb.absa.oss -n migration-e2e legacy-embedded -o jsonpath='{.metadata.finalizers}'Delete legacy object:
kubectl delete gslb.k8gb.absa.oss -n migration-e2e legacy-embeddedValidate:
kubectl get ingress -n migration-e2e legacy-embedded
kubectl get ingress -n migration-e2e legacy-embedded -o json | jq '.metadata.ownerReferences'
# Expected to fail with NotFound (legacy object should be gone after finalizer cleanup)
kubectl get gslb.k8gb.absa.oss -n migration-e2e legacy-embedded || trueExpected:
- Ingress still exists.
- OwnerReferences do not contain the deleted legacy GSLB UID.
- Legacy object is deleted after cleanup.
Apply an already-migrated legacy object:
kubectl apply -f - <<'EOF_CASE5'
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: already-migrated
namespace: migration-e2e
labels:
k8gb.io/migrated-to-k8gb-io: "true"
spec:
strategy:
type: roundRobin
resourceRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: already-migrated
EOF_CASE5Validate:
# Expected to fail with NotFound (controller should ignore already-migrated legacy objects)
kubectl get gslb.k8gb.io -n migration-e2e already-migrated || true
kubectl get events -n migration-e2e --sort-by=.lastTimestamp | grep -E 'LegacyIgnored|already-migrated'Expected:
- Canonical object is not created automatically for this case.
LegacyIgnoredevent is emitted.
Apply embedded legacy GSLB without creating a matching Ingress resource:
kubectl apply -f - <<'EOF_CASE6'
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: embedded-no-ingress
namespace: migration-e2e
spec:
ingress:
ingressClassName: nginx
rules:
- host: embedded-no-ingress.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
strategy:
type: roundRobin
EOF_CASE6Request migration:
kubectl label gslb.k8gb.absa.oss -n migration-e2e embedded-no-ingress k8gb.io/migration-requested=true --overwriteValidate:
kubectl get gslb.k8gb.io -n migration-e2e embedded-no-ingress
kubectl get gslb.k8gb.absa.oss -n migration-e2e embedded-no-ingress -o jsonpath='{.metadata.labels.k8gb\.io/migrated-to-k8gb-io}'Expected:
- Canonical object exists even though Ingress is missing.
- Legacy label equals
true.
Apply already-migrated embedded legacy object:
kubectl apply -f - <<'EOF_CASE7'
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: stale-owner
namespace: migration-e2e
labels:
k8gb.io/migrated-to-k8gb-io: "true"
spec:
ingress:
ingressClassName: nginx
rules:
- host: stale-owner.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
strategy:
type: roundRobin
EOF_CASE7Create an Ingress with a stale ownerReference to the legacy GSLB UID, then retrigger reconcile:
LEGACY_UID=$(kubectl get gslb.k8gb.absa.oss -n migration-e2e stale-owner -o jsonpath='{.metadata.uid}')
kubectl apply -f - <<EOF_CASE7_ING
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: stale-owner
namespace: migration-e2e
ownerReferences:
- apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
name: stale-owner
uid: ${LEGACY_UID}
spec:
ingressClassName: nginx
rules:
- host: stale-owner.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
EOF_CASE7_ING
kubectl annotate gslb.k8gb.absa.oss -n migration-e2e stale-owner migration-recheck-ts="$(date +%s)" --overwriteValidate:
kubectl get ingress -n migration-e2e stale-owner -o json | jq '.metadata.ownerReferences'Expected:
- Legacy ownerReference is removed from the Ingress.
Create canonical object first:
kubectl apply -f - <<'EOF_CASE8_CANONICAL'
apiVersion: k8gb.io/v1beta1
kind: Gslb
metadata:
name: canonical-embedded-cleanup
namespace: migration-e2e
spec:
strategy:
type: failover
primaryGeoTag: eu
resourceRef:
apiVersion: networking.k8s.io/v1
kind: Ingress
name: canonical-embedded-cleanup
EOF_CASE8_CANONICALApply embedded legacy object with same name:
kubectl apply -f - <<'EOF_CASE8_LEGACY'
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: canonical-embedded-cleanup
namespace: migration-e2e
spec:
ingress:
ingressClassName: nginx
rules:
- host: canonical-embedded-cleanup.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
strategy:
type: roundRobin
EOF_CASE8_LEGACYRequest migration:
kubectl label gslb.k8gb.absa.oss -n migration-e2e canonical-embedded-cleanup k8gb.io/migration-requested=true --overwriteInject stale ownerReference and retrigger reconcile:
LEGACY_UID=$(kubectl get gslb.k8gb.absa.oss -n migration-e2e canonical-embedded-cleanup -o jsonpath='{.metadata.uid}')
kubectl apply -f - <<EOF_CASE8_ING
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: canonical-embedded-cleanup
namespace: migration-e2e
ownerReferences:
- apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
name: canonical-embedded-cleanup
uid: ${LEGACY_UID}
spec:
ingressClassName: nginx
rules:
- host: canonical-embedded-cleanup.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
EOF_CASE8_ING
kubectl annotate gslb.k8gb.absa.oss -n migration-e2e canonical-embedded-cleanup migration-recheck-ts="$(date +%s)" --overwriteValidate:
kubectl get gslb.k8gb.io -n migration-e2e canonical-embedded-cleanup -o jsonpath='{.spec.strategy.type}'
kubectl get ingress -n migration-e2e canonical-embedded-cleanup -o json | jq '.metadata.ownerReferences'Expected:
- Canonical strategy stays
failover(not overwritten). - Legacy ownerReference is removed from Ingress.
Apply embedded legacy object:
kubectl apply -f - <<'EOF_CASE9'
apiVersion: k8gb.absa.oss/v1beta1
kind: Gslb
metadata:
name: delete-no-ingress
namespace: migration-e2e
spec:
ingress:
ingressClassName: nginx
rules:
- host: delete-no-ingress.cloud.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: dummy
port:
number: 80
strategy:
type: roundRobin
EOF_CASE9Ensure no matching Ingress exists, then delete legacy object:
kubectl delete ingress -n migration-e2e delete-no-ingress --ignore-not-found
kubectl delete gslb.k8gb.absa.oss -n migration-e2e delete-no-ingress
# Expected to fail with NotFound once deletion completes
kubectl get gslb.k8gb.absa.oss -n migration-e2e delete-no-ingress || trueExpected:
- Legacy object deletion is not stuck in
Terminating. - Finalizer cleanup completes even without Ingress.
Run this on a real upgrade path (cluster already running older release with legacy GSLBs).
kubectl get gslb.k8gb.absa.oss -A -o yaml > pre-legacy-gslb.yaml
kubectl get gslb.k8gb.io -A -o yaml > pre-canonical-gslb.yaml
kubectl get ingress -A -o yaml > pre-ingress.yamlUse your standard Helm upgrade flow for the target version.
Request migration for legacy objects you want to migrate in this wave:
kubectl get gslb.k8gb.absa.oss -A -o json \
| jq -r '.items[] | [.metadata.namespace, .metadata.name] | @tsv' \
| while IFS=$'\t' read -r ns name; do
kubectl label gslb.k8gb.absa.oss -n "$ns" "$name" k8gb.io/migration-requested=true --overwrite
doneVerify every legacy object has matching canonical object:
kubectl get gslb.k8gb.absa.oss -A -o json \
| jq -r '.items[] | [.metadata.namespace, .metadata.name] | @tsv' \
| while IFS=$'\t' read -r ns name; do
kubectl get gslb.k8gb.io -n "$ns" "$name" >/dev/null || echo "MISSING canonical: $ns/$name"
doneVerify migration labels:
kubectl get gslb.k8gb.absa.oss -A -o json \
| jq -r '.items[] | select(.metadata.labels["k8gb.io/migrated-to-k8gb-io"] != "true") | "UNLABELED: \(.metadata.namespace)/\(.metadata.name)"'Check migration events:
kubectl get events -A --sort-by=.lastTimestamp | grep -E 'LegacyMigrated|LegacyIgnored'Pick at least one migrated embedded legacy GSLB and delete the legacy object:
kubectl delete gslb.k8gb.absa.oss -n <ns> <name>
kubectl get ingress -n <ns> <name>Expected:
- Ingress remains present after legacy deletion.
kubectl delete ns migration-e2e