package main import ( "context" app "k8s.io/api/apps/v1" core "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" "path/filepath" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" "testing" ) func TestAPIServerConvergence(t *testing.T) { if os.Getenv("KUBEBUILDER_ASSETS") == "" { t.Skip("set KUBEBUILDER_ASSETS to run envtest") } env := envtest.Environment{CRDDirectoryPaths: []string{filepath.Join("..", "..", "k8s", "envtest")}, ErrorIfCRDPathMissing: true} cfg, err := env.Start() if err != nil { t.Fatal(err) } defer env.Stop() c, err := client.New(cfg, client.Options{}) if err != nil { t.Fatal(err) } ctx := context.Background() ns := "modeldock-lab" if err = c.Create(ctx, &core.Namespace{ObjectMeta: meta.ObjectMeta{Name: ns}}); err != nil { t.Fatal(err) } u := resource() u.SetName("case") u.SetNamespace(ns) u.Object["spec"] = map[string]any{"image": "nginx:stable-alpine", "replicas": int64(1)} if err = c.Create(ctx, u); err != nil { t.Fatal(err) } req := ctrl.Request{NamespacedName: client.ObjectKeyFromObject(u)} r := Controller{c} tick := func() { t.Helper() if _, err := r.Reconcile(ctx, req); err != nil { t.Fatal(err) } } tick() tick() d := &app.Deployment{} if err = c.Get(ctx, req.NamespacedName, d); err != nil { t.Fatal(err) } rv := d.ResourceVersion tick() if err := c.Get(ctx, req.NamespacedName, d); err != nil { t.Fatal(err) } if rv != d.ResourceVersion { t.Fatal("unchanged child rewritten") } if !meta.IsControlledBy(d, u) { t.Fatal("missing owner") } if err := c.Get(ctx, req.NamespacedName, u); err != nil { t.Fatal(err) } phase, _, _ := unstructured.NestedString(u.Object, "status", "phase") if phase != "Progressing" { t.Fatal(phase) } // Simulate a second writer changing the declaration after our read. stale := u.DeepCopy() if err := unstructured.SetNestedField(u.Object, int64(2), "spec", "replicas"); err != nil { t.Fatal(err) } if err := c.Update(ctx, u); err != nil { t.Fatal(err) } stale.SetAnnotations(map[string]string{"study": "stale"}) if err := c.Update(ctx, stale); !apierrors.IsConflict(err) { t.Fatalf("expected conflict, got %v", err) } // A fresh reconciler gets only a key, not the event that changed replicas. r = Controller{c} tick() if err := c.Get(ctx, req.NamespacedName, d); err != nil { t.Fatal(err) } if *d.Spec.Replicas != 2 { t.Fatalf("stale desired replicas: %d", *d.Spec.Replicas) } }