1
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
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
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
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
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
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
136
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
161 newObj.SetGroupVersionKind(mapping.GroupVersionKind)
162
163 rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace)
164
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 }
218
View as plain text