k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/garbagecollector/cluster_scoped_owner_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package garbagecollector 18 19 import ( 20 "context" 21 "io" 22 "net/http" 23 "strings" 24 "testing" 25 "time" 26 27 "k8s.io/api/core/v1" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 "k8s.io/apimachinery/pkg/api/resource" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/wait" 33 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 34 "k8s.io/kubernetes/test/integration/framework" 35 ) 36 37 type roundTripFunc func(req *http.Request) (*http.Response, error) 38 39 func (w roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { 40 return w(req) 41 } 42 43 type readDelayer struct { 44 delay time.Duration 45 io.ReadCloser 46 } 47 48 func (b *readDelayer) Read(p []byte) (n int, err error) { 49 defer time.Sleep(b.delay) 50 return b.ReadCloser.Read(p) 51 } 52 53 func TestClusterScopedOwners(t *testing.T) { 54 // Start the test server and wrap the client to delay PV watch responses 55 server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 56 server.ClientConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { 57 return roundTripFunc(func(req *http.Request) (*http.Response, error) { 58 if req.URL.Query().Get("watch") != "true" || !strings.Contains(req.URL.String(), "persistentvolumes") { 59 return rt.RoundTrip(req) 60 } 61 resp, err := rt.RoundTrip(req) 62 if err != nil { 63 return resp, err 64 } 65 resp.Body = &readDelayer{30 * time.Second, resp.Body} 66 return resp, err 67 }) 68 } 69 ctx := setupWithServer(t, server, 5) 70 defer ctx.tearDown() 71 72 _, clientSet := ctx.gc, ctx.clientSet 73 74 ns := createNamespaceOrDie("gc-cluster-scope-deletion", clientSet, t) 75 defer deleteNamespaceOrDie(ns.Name, clientSet, t) 76 77 t.Log("Create a pair of objects") 78 pv, err := clientSet.CoreV1().PersistentVolumes().Create(context.TODO(), &v1.PersistentVolume{ 79 ObjectMeta: metav1.ObjectMeta{Name: "pv-valid"}, 80 Spec: v1.PersistentVolumeSpec{ 81 PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{Path: "/foo"}}, 82 Capacity: v1.ResourceList{v1.ResourceStorage: resource.MustParse("1Gi")}, 83 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}, 84 }, 85 }, metav1.CreateOptions{}) 86 if err != nil { 87 t.Fatal(err) 88 } 89 if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Create(context.TODO(), &v1.ConfigMap{ 90 ObjectMeta: metav1.ObjectMeta{ 91 Name: "cm-valid", 92 OwnerReferences: []metav1.OwnerReference{{Kind: "PersistentVolume", APIVersion: "v1", Name: pv.Name, UID: pv.UID}}, 93 }, 94 }, metav1.CreateOptions{}); err != nil { 95 t.Fatal(err) 96 } 97 98 t.Log("Create a namespaced object with a missing parent") 99 if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Create(context.TODO(), &v1.ConfigMap{ 100 ObjectMeta: metav1.ObjectMeta{ 101 Name: "cm-missing", 102 Labels: map[string]string{"missing": "true"}, 103 OwnerReferences: []metav1.OwnerReference{{Kind: "PersistentVolume", APIVersion: "v1", Name: "missing-name", UID: types.UID("missing-uid")}}, 104 }, 105 }, metav1.CreateOptions{}); err != nil { 106 t.Fatal(err) 107 } 108 109 t.Log("Create a namespaced object with a missing type parent") 110 if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Create(context.TODO(), &v1.ConfigMap{ 111 ObjectMeta: metav1.ObjectMeta{ 112 Name: "cm-invalid", 113 OwnerReferences: []metav1.OwnerReference{{Kind: "UnknownType", APIVersion: "unknown.group/v1", Name: "invalid-name", UID: types.UID("invalid-uid")}}, 114 }, 115 }, metav1.CreateOptions{}); err != nil { 116 t.Fatal(err) 117 } 118 119 // wait for deletable children to go away 120 if err := wait.Poll(5*time.Second, 300*time.Second, func() (bool, error) { 121 _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Get(context.TODO(), "cm-missing", metav1.GetOptions{}) 122 switch { 123 case apierrors.IsNotFound(err): 124 return true, nil 125 case err != nil: 126 return false, err 127 default: 128 t.Logf("cm with missing parent still exists, retrying") 129 return false, nil 130 } 131 }); err != nil { 132 t.Fatal(err) 133 } 134 t.Logf("deletable children removed") 135 136 // Give time for blocked children to be incorrectly cleaned up 137 time.Sleep(5 * time.Second) 138 139 // ensure children with unverifiable parents don't get reaped 140 if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Get(context.TODO(), "cm-invalid", metav1.GetOptions{}); err != nil { 141 t.Fatalf("child with invalid ownerRef is unexpectedly missing: %v", err) 142 } 143 144 // ensure children with present parents don't get reaped 145 if _, err := clientSet.CoreV1().ConfigMaps(ns.Name).Get(context.TODO(), "cm-valid", metav1.GetOptions{}); err != nil { 146 t.Fatalf("child with valid ownerRef is unexpectedly missing: %v", err) 147 } 148 }