k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/apiserver/apply/status_test.go (about) 1 /* 2 Copyright 2020 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 apiserver 18 19 import ( 20 "context" 21 "encoding/json" 22 "strings" 23 "testing" 24 25 v1 "k8s.io/api/core/v1" 26 apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 27 "k8s.io/apimachinery/pkg/api/meta" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/runtime/schema" 31 "k8s.io/client-go/dynamic" 32 "k8s.io/client-go/kubernetes" 33 apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 34 "k8s.io/kubernetes/test/integration/etcd" 35 "k8s.io/kubernetes/test/integration/framework" 36 ) 37 38 // namespace used for all tests, do not change this 39 const testNamespace = "statusnamespace" 40 41 var statusData = map[schema.GroupVersionResource]string{ 42 gvr("", "v1", "persistentvolumes"): `{"status": {"message": "hello"}}`, 43 gvr("", "v1", "resourcequotas"): `{"status": {"used": {"cpu": "5M"}}}`, 44 gvr("", "v1", "services"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`, 45 gvr("extensions", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`, 46 gvr("networking.k8s.io", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`, 47 gvr("networking.k8s.io", "v1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`, 48 gvr("autoscaling", "v1", "horizontalpodautoscalers"): `{"status": {"currentReplicas": 5}}`, 49 gvr("autoscaling", "v2", "horizontalpodautoscalers"): `{"status": {"currentReplicas": 5}}`, 50 gvr("batch", "v1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`, 51 gvr("batch", "v1beta1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`, 52 gvr("storage.k8s.io", "v1", "volumeattachments"): `{"status": {"attached": true}}`, 53 gvr("policy", "v1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`, 54 gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`, 55 gvr("resource.k8s.io", "v1alpha2", "podschedulingcontexts"): `{"status": {"resourceClaims": [{"name": "my-claim", "unsuitableNodes": ["node1"]}]}}`, 56 gvr("resource.k8s.io", "v1alpha2", "resourceclaims"): `{"status": {"driverName": "example.com"}}`, 57 gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`, 58 // standard for []metav1.Condition 59 gvr("admissionregistration.k8s.io", "v1alpha1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`, 60 gvr("admissionregistration.k8s.io", "v1beta1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`, 61 gvr("admissionregistration.k8s.io", "v1", "validatingadmissionpolicies"): `{"status": {"conditions":[{"type":"Accepted","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"RuleApplied","message":"Rule was applied"}]}}`, 62 } 63 64 const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"True"}]}}` 65 66 func gvr(g, v, r string) schema.GroupVersionResource { 67 return schema.GroupVersionResource{Group: g, Version: v, Resource: r} 68 } 69 70 func createMapping(groupVersion string, resource metav1.APIResource) (*meta.RESTMapping, error) { 71 gv, err := schema.ParseGroupVersion(groupVersion) 72 if err != nil { 73 return nil, err 74 } 75 if len(resource.Group) > 0 || len(resource.Version) > 0 { 76 gv = schema.GroupVersion{ 77 Group: resource.Group, 78 Version: resource.Version, 79 } 80 } 81 gvk := gv.WithKind(resource.Kind) 82 gvr := gv.WithResource(strings.TrimSuffix(resource.Name, "/status")) 83 scope := meta.RESTScopeRoot 84 if resource.Namespaced { 85 scope = meta.RESTScopeNamespace 86 } 87 return &meta.RESTMapping{ 88 Resource: gvr, 89 GroupVersionKind: gvk, 90 Scope: scope, 91 }, nil 92 } 93 94 // TestApplyStatus makes sure that applying the status works for all known types. 95 func TestApplyStatus(t *testing.T) { 96 server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), []string{"--disable-admission-plugins", "ServiceAccount,TaintNodesByCondition"}, framework.SharedEtcd()) 97 if err != nil { 98 t.Fatal(err) 99 } 100 defer server.TearDownFn() 101 102 client, err := kubernetes.NewForConfig(server.ClientConfig) 103 if err != nil { 104 t.Fatal(err) 105 } 106 dynamicClient, err := dynamic.NewForConfig(server.ClientConfig) 107 if err != nil { 108 t.Fatal(err) 109 } 110 111 // create CRDs so we can make sure that custom resources do not get lost 112 etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...) 113 if _, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}, metav1.CreateOptions{}); err != nil { 114 t.Fatal(err) 115 } 116 117 createData := etcd.GetEtcdStorageData() 118 119 // gather resources to test 120 _, resourceLists, err := client.Discovery().ServerGroupsAndResources() 121 if err != nil { 122 t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err) 123 } 124 125 for _, resourceList := range resourceLists { 126 for _, resource := range resourceList.APIResources { 127 if !strings.HasSuffix(resource.Name, "/status") { 128 continue 129 } 130 mapping, err := createMapping(resourceList.GroupVersion, resource) 131 if err != nil { 132 t.Fatal(err) 133 } 134 t.Run(mapping.Resource.String(), func(t *testing.T) { 135 // both spec and status get wiped for CSRs, 136 // nothing is expected to be managed for it, skip it 137 if mapping.Resource.Resource == "certificatesigningrequests" { 138 t.Skip() 139 } 140 141 status, ok := statusData[mapping.Resource] 142 if !ok { 143 status = statusDefault 144 } 145 newResource, ok := createData[mapping.Resource] 146 if !ok { 147 t.Fatalf("no test data for %s. Please add a test for your new type to etcd.GetEtcdStorageData().", mapping.Resource) 148 } 149 newObj := unstructured.Unstructured{} 150 if err := json.Unmarshal([]byte(newResource.Stub), &newObj.Object); err != nil { 151 t.Fatal(err) 152 } 153 154 namespace := testNamespace 155 if mapping.Scope == meta.RESTScopeRoot { 156 namespace = "" 157 } 158 name := newObj.GetName() 159 160 // etcd test stub data doesn't contain apiVersion/kind (!), but apply requires it 161 newObj.SetGroupVersionKind(mapping.GroupVersionKind) 162 163 rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace) 164 // apply to create 165 _, err = rsc.Apply(context.TODO(), name, &newObj, metav1.ApplyOptions{FieldManager: "create_test"}) 166 if err != nil { 167 t.Fatal(err) 168 } 169 170 statusObj := unstructured.Unstructured{} 171 if err := json.Unmarshal([]byte(status), &statusObj.Object); err != nil { 172 t.Fatal(err) 173 } 174 statusObj.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String()) 175 statusObj.SetKind(mapping.GroupVersionKind.Kind) 176 statusObj.SetName(name) 177 178 obj, err := dynamicClient. 179 Resource(mapping.Resource). 180 Namespace(namespace). 181 ApplyStatus(context.TODO(), name, &statusObj, metav1.ApplyOptions{FieldManager: "apply_status_test", Force: true}) 182 if err != nil { 183 t.Fatalf("Failed to apply: %v", err) 184 } 185 186 accessor, err := meta.Accessor(obj) 187 if err != nil { 188 t.Fatalf("Failed to get meta accessor: %v:\n%v", err, obj) 189 } 190 191 managedFields := accessor.GetManagedFields() 192 if managedFields == nil { 193 t.Fatal("Empty managed fields") 194 } 195 if !findManager(managedFields, "apply_status_test") { 196 t.Fatalf("Couldn't find apply_status_test: %v", managedFields) 197 } 198 if !findManager(managedFields, "create_test") { 199 t.Fatalf("Couldn't find create_test: %v", managedFields) 200 } 201 202 if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil { 203 t.Fatalf("deleting final object failed: %v", err) 204 } 205 }) 206 } 207 } 208 } 209 210 func findManager(managedFields []metav1.ManagedFieldsEntry, manager string) bool { 211 for _, entry := range managedFields { 212 if entry.Manager == manager { 213 return true 214 } 215 } 216 return false 217 }