k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/client/dynamic_client_test.go (about) 1 /* 2 Copyright 2016 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 client 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "testing" 25 "time" 26 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 30 "k8s.io/apimachinery/pkg/fields" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/util/wait" 35 "k8s.io/apimachinery/pkg/watch" 36 metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" 37 "k8s.io/client-go/discovery" 38 "k8s.io/client-go/dynamic" 39 clientset "k8s.io/client-go/kubernetes" 40 clientscheme "k8s.io/client-go/kubernetes/scheme" 41 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 42 "k8s.io/kubernetes/test/integration/framework" 43 ) 44 45 func TestDynamicClient(t *testing.T) { 46 result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 47 defer result.TearDownFn() 48 49 client := clientset.NewForConfigOrDie(result.ClientConfig) 50 dynamicClient, err := dynamic.NewForConfig(result.ClientConfig) 51 if err != nil { 52 t.Fatalf("unexpected error creating dynamic client: %v", err) 53 } 54 55 resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} 56 57 // Create a Pod with the normal client 58 pod := &v1.Pod{ 59 ObjectMeta: metav1.ObjectMeta{ 60 GenerateName: "test", 61 }, 62 Spec: v1.PodSpec{ 63 Containers: []v1.Container{ 64 { 65 Name: "test", 66 Image: "test-image", 67 }, 68 }, 69 }, 70 } 71 72 actual, err := client.CoreV1().Pods("default").Create(context.TODO(), pod, metav1.CreateOptions{}) 73 if err != nil { 74 t.Fatalf("unexpected error when creating pod: %v", err) 75 } 76 77 // check dynamic list 78 unstructuredList, err := dynamicClient.Resource(resource).Namespace("default").List(context.TODO(), metav1.ListOptions{}) 79 if err != nil { 80 t.Fatalf("unexpected error when listing pods: %v", err) 81 } 82 83 if len(unstructuredList.Items) != 1 { 84 t.Fatalf("expected one pod, got %d", len(unstructuredList.Items)) 85 } 86 87 got, err := unstructuredToPod(&unstructuredList.Items[0]) 88 if err != nil { 89 t.Fatalf("unexpected error converting Unstructured to v1.Pod: %v", err) 90 } 91 92 if !reflect.DeepEqual(actual, got) { 93 t.Fatalf("unexpected pod in list. wanted %#v, got %#v", actual, got) 94 } 95 96 // check dynamic get 97 unstruct, err := dynamicClient.Resource(resource).Namespace("default").Get(context.TODO(), actual.Name, metav1.GetOptions{}) 98 if err != nil { 99 t.Fatalf("unexpected error when getting pod %q: %v", actual.Name, err) 100 } 101 102 got, err = unstructuredToPod(unstruct) 103 if err != nil { 104 t.Fatalf("unexpected error converting Unstructured to v1.Pod: %v", err) 105 } 106 107 if !reflect.DeepEqual(actual, got) { 108 t.Fatalf("unexpected pod in list. wanted %#v, got %#v", actual, got) 109 } 110 111 // delete the pod dynamically 112 err = dynamicClient.Resource(resource).Namespace("default").Delete(context.TODO(), actual.Name, metav1.DeleteOptions{}) 113 if err != nil { 114 t.Fatalf("unexpected error when deleting pod: %v", err) 115 } 116 117 list, err := client.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{}) 118 if err != nil { 119 t.Fatalf("unexpected error when listing pods: %v", err) 120 } 121 122 if len(list.Items) != 0 { 123 t.Fatalf("expected zero pods, got %d", len(list.Items)) 124 } 125 } 126 127 func TestDynamicClientWatch(t *testing.T) { 128 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 129 defer result.TearDownFn() 130 131 client := clientset.NewForConfigOrDie(result.ClientConfig) 132 dynamicClient, err := dynamic.NewForConfig(result.ClientConfig) 133 if err != nil { 134 t.Fatalf("unexpected error creating dynamic client: %v", err) 135 } 136 137 resource := v1.SchemeGroupVersion.WithResource("events") 138 139 mkEvent := func(i int) *v1.Event { 140 name := fmt.Sprintf("event-%v", i) 141 return &v1.Event{ 142 ObjectMeta: metav1.ObjectMeta{ 143 Namespace: "default", 144 Name: name, 145 }, 146 InvolvedObject: v1.ObjectReference{ 147 Namespace: "default", 148 Name: name, 149 }, 150 Reason: fmt.Sprintf("event %v", i), 151 } 152 } 153 154 rv1 := "" 155 for i := 0; i < 10; i++ { 156 event := mkEvent(i) 157 got, err := client.CoreV1().Events("default").Create(context.TODO(), event, metav1.CreateOptions{}) 158 if err != nil { 159 t.Fatalf("Failed creating event %#q: %v", event, err) 160 } 161 if rv1 == "" { 162 rv1 = got.ResourceVersion 163 if rv1 == "" { 164 t.Fatal("did not get a resource version.") 165 } 166 } 167 t.Logf("Created event %#v", got.ObjectMeta) 168 } 169 170 w, err := dynamicClient.Resource(resource).Namespace("default").Watch(context.TODO(), metav1.ListOptions{ 171 ResourceVersion: rv1, 172 Watch: true, 173 FieldSelector: fields.OneTermEqualSelector("metadata.name", "event-9").String(), 174 }) 175 176 if err != nil { 177 t.Fatalf("Failed watch: %v", err) 178 } 179 defer w.Stop() 180 181 select { 182 case <-time.After(wait.ForeverTestTimeout): 183 t.Fatalf("watch took longer than %s", wait.ForeverTestTimeout.String()) 184 case got, ok := <-w.ResultChan(): 185 if !ok { 186 t.Fatal("Watch channel closed unexpectedly.") 187 } 188 189 // We expect to see an ADD of event-9 and only event-9. (This 190 // catches a bug where all the events would have been sent down 191 // the channel.) 192 if e, a := watch.Added, got.Type; e != a { 193 t.Errorf("Wanted %v, got %v", e, a) 194 } 195 196 unstructured, ok := got.Object.(*unstructured.Unstructured) 197 if !ok { 198 t.Fatalf("Unexpected watch event containing object %#q", got.Object) 199 } 200 event, err := unstructuredToEvent(unstructured) 201 if err != nil { 202 t.Fatalf("unexpected error converting Unstructured to v1.Event: %v", err) 203 } 204 if e, a := "event-9", event.Name; e != a { 205 t.Errorf("Wanted %v, got %v", e, a) 206 } 207 } 208 } 209 210 func TestUnstructuredExtract(t *testing.T) { 211 result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 212 defer result.TearDownFn() 213 214 dynamicClient, err := dynamic.NewForConfig(result.ClientConfig) 215 if err != nil { 216 t.Fatalf("unexpected error creating dynamic client: %v", err) 217 } 218 219 resource := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} 220 221 // Apply an unstructured with the dynamic client 222 name := "test-pod" 223 pod := &unstructured.Unstructured{ 224 Object: map[string]interface{}{ 225 "apiVersion": "v1", 226 "kind": "Pod", 227 "metadata": map[string]interface{}{ 228 "name": name, 229 // namespace will always get set by extract, 230 // so we add it here (even though it's optional) 231 // to ensure what we apply equals what we extract. 232 "namespace": "default", 233 }, 234 "spec": map[string]interface{}{ 235 "containers": []interface{}{ 236 map[string]interface{}{ 237 "name": "test", 238 "image": "test-image", 239 }, 240 }, 241 }, 242 }, 243 } 244 mgr := "testManager" 245 podData, err := json.Marshal(pod) 246 if err != nil { 247 t.Fatalf("failed to marshal pod into bytes: %v", err) 248 } 249 250 // apply the unstructured object to the cluster 251 actual, err := dynamicClient.Resource(resource).Namespace("default").Patch( 252 context.TODO(), 253 name, 254 types.ApplyPatchType, 255 podData, 256 metav1.PatchOptions{FieldManager: mgr}) 257 if err != nil { 258 t.Fatalf("unexpected error when creating pod: %v", err) 259 } 260 261 // extract the object 262 discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(result.ClientConfig) 263 extractor, err := metav1ac.NewUnstructuredExtractor(discoveryClient) 264 if err != nil { 265 t.Fatalf("unexpected error when constructing extrator: %v", err) 266 } 267 extracted, err := extractor.Extract(actual, mgr) 268 if err != nil { 269 t.Fatalf("unexpected error when extracting: %v", err) 270 } 271 272 // confirm that the extracted object equals the applied object 273 if !reflect.DeepEqual(pod, extracted) { 274 t.Fatalf("extracted pod doesn't equal applied pod. wanted:\n %v\n, got:\n %v\n", pod, extracted) 275 } 276 277 } 278 279 func unstructuredToPod(obj *unstructured.Unstructured) (*v1.Pod, error) { 280 json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 281 if err != nil { 282 return nil, err 283 } 284 pod := new(v1.Pod) 285 err = runtime.DecodeInto(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), json, pod) 286 pod.Kind = "" 287 pod.APIVersion = "" 288 return pod, err 289 } 290 291 func unstructuredToEvent(obj *unstructured.Unstructured) (*v1.Event, error) { 292 json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) 293 if err != nil { 294 return nil, err 295 } 296 event := new(v1.Event) 297 err = runtime.DecodeInto(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), json, event) 298 return event, err 299 }