github.com/felipejfc/helm@v2.1.2+incompatible/pkg/kube/client_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors All rights reserved. 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 kube 18 19 import ( 20 "bytes" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "strings" 25 "testing" 26 27 "k8s.io/kubernetes/pkg/api" 28 "k8s.io/kubernetes/pkg/api/testapi" 29 "k8s.io/kubernetes/pkg/api/unversioned" 30 "k8s.io/kubernetes/pkg/api/validation" 31 "k8s.io/kubernetes/pkg/client/restclient/fake" 32 cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" 33 "k8s.io/kubernetes/pkg/kubectl/resource" 34 "k8s.io/kubernetes/pkg/runtime" 35 ) 36 37 func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { 38 return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) 39 } 40 41 func newPod(name string) api.Pod { 42 return api.Pod{ 43 ObjectMeta: api.ObjectMeta{Name: name}, 44 Spec: api.PodSpec{ 45 Containers: []api.Container{{ 46 Name: "app:v4", 47 Image: "abc/app:v4", 48 Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}}, 49 }}, 50 }, 51 } 52 } 53 54 func newPodList(names ...string) api.PodList { 55 var list api.PodList 56 for _, name := range names { 57 list.Items = append(list.Items, newPod(name)) 58 } 59 return list 60 } 61 62 func notFoundBody() *unversioned.Status { 63 return &unversioned.Status{ 64 Code: http.StatusNotFound, 65 Status: unversioned.StatusFailure, 66 Reason: unversioned.StatusReasonNotFound, 67 Message: " \"\" not found", 68 Details: &unversioned.StatusDetails{}, 69 } 70 } 71 72 func newResponse(code int, obj runtime.Object) (*http.Response, error) { 73 header := http.Header{} 74 header.Set("Content-Type", runtime.ContentTypeJSON) 75 body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj)))) 76 return &http.Response{StatusCode: code, Header: header, Body: body}, nil 77 } 78 79 func TestUpdate(t *testing.T) { 80 listA := newPodList("starfish", "otter", "squid") 81 listB := newPodList("starfish", "otter", "dolphin") 82 listB.Items[0].Spec.Containers[0].Ports = []api.ContainerPort{{Name: "https", ContainerPort: 443}} 83 84 actions := make(map[string]string) 85 86 f, tf, codec, ns := cmdtesting.NewAPIFactory() 87 tf.Client = &fake.RESTClient{ 88 NegotiatedSerializer: ns, 89 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 90 p, m := req.URL.Path, req.Method 91 actions[p] = m 92 switch { 93 case p == "/namespaces/test/pods/starfish" && m == "GET": 94 return newResponse(200, &listA.Items[0]) 95 case p == "/namespaces/test/pods/otter" && m == "GET": 96 return newResponse(200, &listA.Items[1]) 97 case p == "/namespaces/test/pods/dolphin" && m == "GET": 98 return newResponse(404, notFoundBody()) 99 case p == "/namespaces/test/pods/starfish" && m == "PATCH": 100 data, err := ioutil.ReadAll(req.Body) 101 if err != nil { 102 t.Fatalf("could not dump request: %s", err) 103 } 104 req.Body.Close() 105 expected := `{"spec":{"containers":[{"name":"app:v4","ports":[{"containerPort":443,"name":"https","protocol":"TCP"},{"$patch":"delete","containerPort":80}]}]}}` 106 if string(data) != expected { 107 t.Errorf("expected patch %s, got %s", expected, string(data)) 108 } 109 return newResponse(200, &listB.Items[0]) 110 case p == "/namespaces/test/pods" && m == "POST": 111 return newResponse(200, &listB.Items[1]) 112 case p == "/namespaces/test/pods/squid" && m == "DELETE": 113 return newResponse(200, &listB.Items[1]) 114 default: 115 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) 116 return nil, nil 117 } 118 }), 119 } 120 121 c := &Client{Factory: f} 122 if err := c.Update("test", objBody(codec, &listA), objBody(codec, &listB)); err != nil { 123 t.Fatal(err) 124 } 125 126 expectedActions := map[string]string{ 127 "/namespaces/test/pods/dolphin": "GET", 128 "/namespaces/test/pods/otter": "GET", 129 "/namespaces/test/pods/starfish": "PATCH", 130 "/namespaces/test/pods": "POST", 131 "/namespaces/test/pods/squid": "DELETE", 132 } 133 134 for k, v := range expectedActions { 135 if m, ok := actions[k]; !ok || m != v { 136 t.Errorf("expected a %s request to %s", k, v) 137 } 138 } 139 } 140 141 func TestPerform(t *testing.T) { 142 tests := []struct { 143 name string 144 namespace string 145 reader io.Reader 146 count int 147 swaggerFile string 148 err bool 149 errMessage string 150 }{ 151 { 152 name: "Valid input", 153 namespace: "test", 154 reader: strings.NewReader(guestbookManifest), 155 count: 6, 156 }, { 157 name: "Empty manifests", 158 namespace: "test", 159 reader: strings.NewReader(""), 160 err: true, 161 errMessage: "no objects visited", 162 }, { 163 name: "Invalid schema", 164 namespace: "test", 165 reader: strings.NewReader(testInvalidServiceManifest), 166 swaggerFile: "../../vendor/k8s.io/kubernetes/api/swagger-spec/" + testapi.Default.GroupVersion().Version + ".json", 167 err: true, 168 errMessage: `error validating "": error validating data: expected type int, for field spec.ports[0].port, got string`, 169 }, 170 } 171 172 for _, tt := range tests { 173 results := []*resource.Info{} 174 175 fn := func(info *resource.Info) error { 176 results = append(results, info) 177 178 if info.Namespace != tt.namespace { 179 t.Errorf("%q. expected namespace to be '%s', got %s", tt.name, tt.namespace, info.Namespace) 180 } 181 return nil 182 } 183 184 f, tf, _, _ := cmdtesting.NewAPIFactory() 185 c := &Client{Factory: f} 186 if tt.swaggerFile != "" { 187 data, err := ioutil.ReadFile(tt.swaggerFile) 188 if err != nil { 189 t.Fatalf("could not read swagger spec: %s", err) 190 } 191 validator, err := validation.NewSwaggerSchemaFromBytes(data, nil) 192 if err != nil { 193 t.Fatalf("could not load swagger spec: %s", err) 194 } 195 tf.Validator = validator 196 } 197 198 err := perform(c, tt.namespace, tt.reader, fn) 199 if (err != nil) != tt.err { 200 t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err) 201 } 202 if err != nil && err.Error() != tt.errMessage { 203 t.Errorf("%q. expected error message: %v, got %v", tt.name, tt.errMessage, err) 204 } 205 206 if len(results) != tt.count { 207 t.Errorf("%q. expected %d result objects, got %d", tt.name, tt.count, len(results)) 208 } 209 } 210 } 211 212 func TestReal(t *testing.T) { 213 t.Skip("This is a live test, comment this line to run") 214 c := New(nil) 215 if err := c.Create("test", strings.NewReader(guestbookManifest)); err != nil { 216 t.Fatal(err) 217 } 218 219 testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest 220 c = New(nil) 221 if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil { 222 t.Fatal(err) 223 } 224 225 if err := c.Delete("test-delete", strings.NewReader(testEndpointManifest)); err != nil { 226 t.Fatal(err) 227 } 228 229 // ensures that delete does not fail if a resource is not found 230 if err := c.Delete("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil { 231 t.Fatal(err) 232 } 233 } 234 235 const testServiceManifest = ` 236 kind: Service 237 apiVersion: v1 238 metadata: 239 name: my-service 240 spec: 241 selector: 242 app: myapp 243 ports: 244 - port: 80 245 protocol: TCP 246 targetPort: 9376 247 ` 248 249 const testInvalidServiceManifest = ` 250 kind: Service 251 apiVersion: v1 252 spec: 253 ports: 254 - port: "80" 255 ` 256 257 const testEndpointManifest = ` 258 kind: Endpoints 259 apiVersion: v1 260 metadata: 261 name: my-service 262 subsets: 263 - addresses: 264 - ip: "1.2.3.4" 265 ports: 266 - port: 9376 267 ` 268 269 const guestbookManifest = ` 270 apiVersion: v1 271 kind: Service 272 metadata: 273 name: redis-master 274 labels: 275 app: redis 276 tier: backend 277 role: master 278 spec: 279 ports: 280 - port: 6379 281 targetPort: 6379 282 selector: 283 app: redis 284 tier: backend 285 role: master 286 --- 287 apiVersion: extensions/v1beta1 288 kind: Deployment 289 metadata: 290 name: redis-master 291 spec: 292 replicas: 1 293 template: 294 metadata: 295 labels: 296 app: redis 297 role: master 298 tier: backend 299 spec: 300 containers: 301 - name: master 302 image: gcr.io/google_containers/redis:e2e # or just image: redis 303 resources: 304 requests: 305 cpu: 100m 306 memory: 100Mi 307 ports: 308 - containerPort: 6379 309 --- 310 apiVersion: v1 311 kind: Service 312 metadata: 313 name: redis-slave 314 labels: 315 app: redis 316 tier: backend 317 role: slave 318 spec: 319 ports: 320 # the port that this service should serve on 321 - port: 6379 322 selector: 323 app: redis 324 tier: backend 325 role: slave 326 --- 327 apiVersion: extensions/v1beta1 328 kind: Deployment 329 metadata: 330 name: redis-slave 331 spec: 332 replicas: 2 333 template: 334 metadata: 335 labels: 336 app: redis 337 role: slave 338 tier: backend 339 spec: 340 containers: 341 - name: slave 342 image: gcr.io/google_samples/gb-redisslave:v1 343 resources: 344 requests: 345 cpu: 100m 346 memory: 100Mi 347 env: 348 - name: GET_HOSTS_FROM 349 value: dns 350 ports: 351 - containerPort: 6379 352 --- 353 apiVersion: v1 354 kind: Service 355 metadata: 356 name: frontend 357 labels: 358 app: guestbook 359 tier: frontend 360 spec: 361 ports: 362 - port: 80 363 selector: 364 app: guestbook 365 tier: frontend 366 --- 367 apiVersion: extensions/v1beta1 368 kind: Deployment 369 metadata: 370 name: frontend 371 spec: 372 replicas: 3 373 template: 374 metadata: 375 labels: 376 app: guestbook 377 tier: frontend 378 spec: 379 containers: 380 - name: php-redis 381 image: gcr.io/google-samples/gb-frontend:v4 382 resources: 383 requests: 384 cpu: 100m 385 memory: 100Mi 386 env: 387 - name: GET_HOSTS_FROM 388 value: dns 389 ports: 390 - containerPort: 80 391 `