github.com/docker/compose-on-kubernetes@v0.5.0/e2e/compose_test.go (about) 1 package e2e 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "math/rand" 9 "strings" 10 "time" 11 12 "github.com/docker/compose-on-kubernetes/api/compose/latest" 13 "github.com/docker/compose-on-kubernetes/api/compose/v1beta1" 14 "github.com/docker/compose-on-kubernetes/api/compose/v1beta2" 15 "github.com/docker/compose-on-kubernetes/api/labels" 16 "github.com/docker/compose-on-kubernetes/internal/e2e/cluster" 17 . "github.com/onsi/ginkgo" // Import ginkgo to simplify test code 18 . "github.com/onsi/gomega" // Import gomega to simplify test code 19 appsv1 "k8s.io/api/apps/v1" 20 corev1 "k8s.io/api/core/v1" 21 apierrors "k8s.io/apimachinery/pkg/api/errors" 22 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 23 "k8s.io/apimachinery/pkg/util/intstr" 24 "k8s.io/apimachinery/pkg/util/wait" 25 "k8s.io/client-go/kubernetes" 26 ) 27 28 const ( 29 portMin = 32768 30 portMax = 35535 31 privateImagePullUsername = "composeonk8simagepull" 32 privateImagePullPassword = "XHWl8mJ6IH5o" 33 privateImagePullImage = "composeonkubernetes/nginx:1.12.1-alpine" 34 ) 35 36 var usedPorts = map[int]struct{}{} 37 38 func getRandomPort() int { 39 candidate := rand.Intn(portMax-portMin) + portMin 40 if _, ok := usedPorts[candidate]; ok { 41 return getRandomPort() 42 } 43 usedPorts[candidate] = struct{}{} 44 return candidate 45 } 46 47 const deployNamespace = "e2e-tests" 48 49 const defaultStrategy = cluster.StackOperationV1beta2Compose 50 51 func scaleStack(s *latest.Stack, service string, replicas int) (*latest.Stack, error) { 52 stack := s.Clone() 53 for i, svc := range stack.Spec.Services { 54 if svc.Name != service { 55 continue 56 } 57 r := uint64(replicas) 58 stack.Spec.Services[i].Deploy.Replicas = &r 59 return stack, nil 60 } 61 return nil, errors.New(service + " not found") 62 } 63 64 func testUpdate(ns *cluster.Namespace, create, update cluster.StackOperationStrategy) { 65 By("Creating a stack") 66 port := getRandomPort() 67 _, err := ns.CreateStack(create, "app", fmt.Sprintf(`version: '3.2' 68 services: 69 front: 70 image: nginx:1.12.1-alpine 71 ports: 72 - %d:80`, port)) 73 expectNoError(err) 74 waitUntil(ns.IsStackAvailable("app")) 75 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 76 77 By("Updating the stack") 78 _, err = ns.UpdateStack(update, "app", fmt.Sprintf(`version: '3.2' 79 services: 80 front: 81 image: httpd:2.4.27-alpine 82 ports: 83 - %d:80`, port)) 84 expectNoError(err) 85 By("Verifying the stack has been updated") 86 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "It works!")) 87 } 88 89 func skipIfNoStorageClass(ns *cluster.Namespace) { 90 h, err := ns.HasStorageClass() 91 expectNoError(err) 92 if !h { 93 Skip("Cluster does not have any storage class") 94 } 95 } 96 97 var _ = Describe("Compose fry", func() { 98 99 var ( 100 ns *cluster.Namespace 101 cleanup func() 102 ) 103 104 BeforeEach(func() { 105 ns, cleanup = createNamespace() 106 }) 107 108 AfterEach(func() { 109 cleanup() 110 }) 111 112 It("Should contain zero stack", func() { 113 waitUntil(ns.ContainsZeroStack()) 114 }) 115 116 It("Should deploy a stack", func() { 117 composeFile := `version: '3.2' 118 services: 119 back: 120 image: nginx:1.12.1-alpine` 121 By("Creating a stack") 122 _, err := ns.CreateStack(defaultStrategy, "app", composeFile) 123 124 expectNoError(err) 125 126 By("Verifying the stack Spec") 127 items, err := ns.ListStacks() 128 129 expectNoError(err) 130 Expect(items).To(HaveLen(1)) 131 Expect(items[0].Name).To(Equal("app")) 132 var cf latest.ComposeFile 133 err = ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("composefile").Do().Into(&cf) 134 expectNoError(err) 135 Expect(cf.ComposeFile).To(Equal(composeFile)) 136 137 waitUntil(ns.ContainsNPods(1)) 138 139 pods, err := ns.ListAllPods() 140 141 expectNoError(err) 142 Expect(pods).To(HaveLen(1)) 143 imgName := pods[0].Spec.Containers[0].Image 144 Expect(imgName).To(ContainSubstring("nginx:1.12.1-alpine")) 145 146 deployments, err := ns.ListDeployments("") 147 148 By("Verifying the Deployment ownerRef") 149 expectNoError(err) 150 Expect(deployments).To(HaveLen(1)) 151 Expect(deployments[0].OwnerReferences).To(HaveLen(1)) 152 153 ownerRef := deployments[0].OwnerReferences[0] 154 155 Expect(ownerRef.Kind).To(Equal("Stack")) 156 Expect(ownerRef.Name).To(Equal(items[0].Name)) 157 Expect(ownerRef.UID).To(Equal(items[0].UID)) 158 }) 159 160 It("Should update a stack (yaml yaml)", func() { testUpdate(ns, cluster.StackOperationV1beta2Compose, cluster.StackOperationV1beta2Compose) }) 161 It("Should update a stack (yaml stack)", func() { testUpdate(ns, cluster.StackOperationV1beta2Compose, cluster.StackOperationV1beta2Stack) }) 162 It("Should update a stack (stack yaml)", func() { testUpdate(ns, cluster.StackOperationV1beta2Stack, cluster.StackOperationV1beta2Compose) }) 163 It("Should update a stack (stack stack)", func() { testUpdate(ns, cluster.StackOperationV1beta2Stack, cluster.StackOperationV1beta2Stack) }) 164 It("Should update a stack (v1alpha3 v1alpha3)", func() { testUpdate(ns, cluster.StackOperationV1alpha3, cluster.StackOperationV1alpha3) }) 165 It("Should update a stack (v1beta2 v1alpha3)", func() { testUpdate(ns, cluster.StackOperationV1beta2Stack, cluster.StackOperationV1alpha3) }) 166 It("Should update a stack (v1beta1 v1alpha3)", func() { testUpdate(ns, cluster.StackOperationV1beta1, cluster.StackOperationV1alpha3) }) 167 168 It("Should update a stack with orphans", func() { 169 _, err := ns.CreateStack(defaultStrategy, "app", `version: '3.2' 170 services: 171 front: 172 image: httpd:2.4.27-alpine 173 orphan: 174 image: nginx`) 175 expectNoError(err) 176 waitUntil(ns.ContainsNPodsMatchingSelector(1, stackServiceLabel("orphan"))) 177 178 _, err = ns.UpdateStack(defaultStrategy, "app", `version: '3.2' 179 services: 180 front: 181 image: nginx`) 182 expectNoError(err) 183 waitUntil(ns.ContainsNPodsMatchingSelector(0, stackServiceLabel("orphan"))) 184 waitUntil(ns.IsServiceNotPresent(stackServiceLabel("orphan"))) 185 }) 186 187 It("Should update Deployement to StatefulSet", func() { 188 _, err := ns.CreateStack(defaultStrategy, "app", `version: '3.2' 189 services: 190 front: 191 image: nginx`) 192 expectNoError(err) 193 194 waitUntil(ns.ContainsNPodsMatchingSelector(1, stackServiceLabel("front"))) 195 196 pods, err := ns.ListPods(stackServiceLabel("front")) 197 expectNoError(err) 198 199 references := pods[0].ObjectMeta.OwnerReferences 200 Expect(references).To(HaveLen(1)) 201 Expect(references[0].Kind).To(Equal("ReplicaSet")) 202 203 _, err = ns.UpdateStack(defaultStrategy, "app", `version: '3.2' 204 services: 205 front: 206 image: nginx 207 volumes: 208 - data:/tmp 209 volumes: 210 data:`) 211 expectNoError(err) 212 213 waitUntil(ns.ContainsNPodsWithPredicate(1, stackServiceLabel("front"), func(pod corev1.Pod) (bool, string) { 214 references := pod.ObjectMeta.OwnerReferences 215 if len(references) != 1 { 216 return false, "Owner reference not updated" 217 } 218 if references[0].Kind != "StatefulSet" { 219 return false, "Wrong owner reference: " + references[0].Kind 220 } 221 return true, "" 222 })) 223 }) 224 225 It("Should remove a stack", func() { 226 _, err := ns.CreateStack(defaultStrategy, "app", `version: '3.2' 227 services: 228 back: 229 image: nginx:1.12.1-alpine`) 230 expectNoError(err) 231 232 waitUntil(ns.ContainsNPods(1)) 233 234 err = ns.DeleteStack("app") 235 expectNoError(err) 236 237 waitUntil(ns.ContainsZeroPod()) 238 }) 239 240 It("Should fail when removing not existing stack", func() { 241 err := ns.DeleteStack("unknown") 242 Expect(err).To(HaveOccurred()) 243 }) 244 245 It("Should scale as expected", func() { 246 spec := `version: '3.2' 247 services: 248 back: 249 image: nginx:1.12.1-alpine` 250 251 stack, err := ns.CreateStack(cluster.StackOperationV1beta2Stack, "app", spec) 252 expectNoError(err) 253 254 waitUntil(ns.ContainsNPods(1)) 255 256 stack, err = scaleStack(stack, "back", 3) 257 expectNoError(err) 258 259 _, err = ns.UpdateStackFromSpec("app", stack) 260 expectNoError(err) 261 262 waitUntil(ns.ContainsNPods(3)) 263 264 stack, err = scaleStack(stack, "back", 1) 265 expectNoError(err) 266 267 _, err = ns.UpdateStackFromSpec("app", stack) 268 expectNoError(err) 269 270 waitUntil(ns.ContainsNPods(1)) 271 }) 272 273 It("Should scale using the scale subresource", func() { 274 spec := `version: '3.2' 275 services: 276 back: 277 image: nginx:1.12.1-alpine` 278 279 _, err := ns.CreateStack(defaultStrategy, "app", spec) 280 expectNoError(err) 281 282 waitUntil(ns.ContainsNPods(1)) 283 scalerV1beta2 := v1beta2.Scale{ 284 ObjectMeta: metav1.ObjectMeta{ 285 Name: "app", 286 }, 287 Spec: map[string]int{"back": 2}, 288 } 289 err = ns.RESTClientV1beta2().Put().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("scale").Body(&scalerV1beta2).Do().Error() 290 expectNoError(err) 291 waitUntil(ns.ContainsNPods(2)) 292 293 scalerV1alpha3 := latest.Scale{ 294 ObjectMeta: metav1.ObjectMeta{ 295 Name: "app", 296 }, 297 Spec: map[string]int{"back": 1}, 298 } 299 err = ns.RESTClientV1alpha3().Put().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("scale").Body(&scalerV1alpha3).Do().Error() 300 expectNoError(err) 301 waitUntil(ns.ContainsNPods(1)) 302 303 scalerV1alpha3.Spec["nope"] = 1 304 err = ns.RESTClientV1alpha3().Put().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("scale").Body(&scalerV1alpha3).Do().Error() 305 Expect(err).To(HaveOccurred()) 306 }) 307 308 It("Should place pods on the expected node", func() { 309 nodes, err := ns.ListNodes() 310 expectNoError(err) 311 Expect(len(nodes)).To(BeNumerically(">=", 1)) 312 hostname := nodes[0].GetLabels()["kubernetes.io/hostname"] 313 314 spec := `version: '3.2' 315 services: 316 back: 317 image: nginx:1.12.1-alpine 318 deploy: 319 replicas: 6 320 placement: 321 constraints: 322 - node.hostname == ` + hostname 323 324 _, err = ns.CreateStack(defaultStrategy, "app", spec) 325 expectNoError(err) 326 327 waitUntil(ns.ContainsNPods(6)) 328 pods, err := ns.ListPods("app") 329 expectNoError(err) 330 for _, pod := range pods { 331 Expect(pod.Spec.Hostname).To(Equal(hostname)) 332 } 333 }) 334 335 It("Should deploy Docker Pets", func() { 336 port0 := getRandomPort() 337 port1 := getRandomPort() 338 port2 := getRandomPort() 339 spec := fmt.Sprintf(`version: '3.1' 340 services: 341 web: 342 #Use following image to download from Docker Hub 343 #image: chrch/docker-pets:latest 344 image: chrch/docker-pets:1.0 345 deploy: 346 mode: replicated 347 replicas: 2 348 healthcheck: 349 interval: 10s 350 timeout: 5s 351 retries: 3 352 ports: 353 - %d:5000 354 - %d:7000 355 environment: 356 DB: 'db' 357 THREADED: 'True' 358 networks: 359 - backend 360 db: 361 image: consul:0.7.2 362 command: agent -server -ui -client=0.0.0.0 -bootstrap-expect=3 -retry-join=db -retry-join=db -retry-join=db -retry-interval 5s 363 deploy: 364 replicas: 3 365 ports: 366 - %d:8500 367 environment: 368 CONSUL_BIND_INTERFACE: 'eth2' 369 CONSUL_LOCAL_CONFIG: '{"skip_leave_on_interrupt": true}' 370 networks: 371 - backend 372 networks: 373 backend: 374 `, port0, port1, port2) 375 _, err := ns.CreateStack(defaultStrategy, "app", spec) 376 expectNoError(err) 377 waitUntil(ns.ContainsNPods(5)) 378 }) 379 380 It("should keep pod accessible on non-exposed port", func() { 381 port0 := getRandomPort() 382 port1 := getRandomPort() 383 spec := fmt.Sprintf(`version: '3.3' 384 385 services: 386 web: 387 build: web 388 image: dockerdemos/lab-web 389 ports: 390 - "%d:80" 391 392 words: 393 build: words 394 image: dockerdemos/lab-words 395 ports: 396 - "%d:8080" 397 deploy: 398 replicas: 5 399 endpoint_mode: dnsrr 400 resources: 401 limits: 402 memory: 64M 403 reservations: 404 memory: 64M 405 406 db: 407 build: db 408 image: dockerdemos/lab-db`, port0, port1) 409 410 _, err := ns.CreateStack(defaultStrategy, "app", spec) 411 expectNoError(err) 412 413 waitUntil(ns.IsServicePresent(stackServiceLabel("web"))) 414 415 services, err := ns.ListServices(stackServiceLabel("web")) 416 expectNoError(err) 417 Expect(len(services)).To(BeNumerically(">=", 1)) 418 419 waitUntil(ns.IsServiceResponding(fmt.Sprintf("web-published:%d-tcp", port0), "/proxy/words/verb", "\"word\"")) 420 }) 421 422 It("Should propagate deploy.labels to pod templates, selectors and services", func() { 423 _, err := ns.CreateStack(cluster.StackOperationV1beta2Stack, "app", `version: "3" 424 services: 425 simple: 426 image: busybox:latest 427 command: top 428 labels: 429 should-be-annotation: annotation 430 deploy: 431 labels: 432 test-label: test-value`) 433 expectNoError(err) 434 435 waitUntil(ns.IsServicePresent(stackServiceLabel("simple"))) 436 services, err := ns.ListServices(stackServiceLabel("simple")) 437 expectNoError(err) 438 deps, err := ns.ListDeployments(stackServiceLabel("simple")) 439 expectNoError(err) 440 441 // check annotation on pod template 442 Expect(deps[0].Spec.Template.Annotations["should-be-annotation"]).To(Equal("annotation")) 443 // check service labels 444 Expect(services[0].Labels["test-label"]).To(Equal("test-value")) 445 // check pod template labels 446 Expect(deps[0].Spec.Template.Labels["test-label"]).To(Equal("test-value")) 447 }) 448 449 It("Should support deploy.labels updates", func() { 450 _, err := ns.CreateStack(cluster.StackOperationV1beta2Stack, "app", `version: "3" 451 services: 452 simple: 453 image: busybox:latest 454 command: top 455 labels: 456 should-be-annotation: annotation 457 deploy: 458 labels: 459 test-label: test-value`) 460 expectNoError(err) 461 waitUntil(ns.IsStackAvailable("app")) 462 _, err = ns.UpdateStack(cluster.StackOperationV1beta2Stack, "app", `version: "3" 463 services: 464 simple: 465 image: nginx:1.13-alpine 466 labels: 467 should-be-annotation: annotation 468 deploy: 469 labels: 470 test-label: test-value-updated 471 second-test: test-value`) 472 expectNoError(err) 473 474 waitUntil(ns.ContainsNPodsWithPredicate(1, stackServiceLabel("simple"), func(pod corev1.Pod) (bool, string) { 475 return pod.Spec.Containers[0].Image == "nginx:1.13-alpine", "" 476 })) 477 478 s, err := ns.GetStack("app") 479 expectNoError(err) 480 Expect(s.Status.Phase).NotTo(Equal(latest.StackFailure)) 481 waitUntil(ns.IsStackAvailable("app")) 482 }) 483 484 It("Should transform ports with the correct rules and keep inter pod communication working", func() { 485 port0 := getRandomPort() 486 port1 := getRandomPort() 487 spec := fmt.Sprintf(`version: '3.3' 488 services: 489 web: 490 image: nginx:1.13-alpine 491 ports: 492 - "%d:80" 493 - "%d:81" 494 - "82" 495 - "83"`, port0, port1) 496 _, err := ns.CreateStack(defaultStrategy, "app", spec) 497 expectNoError(err) 498 waitUntil(ns.ServiceCount(stackServiceLabel("web"), 3)) 499 services, err := ns.ListServices(stackServiceLabel("web")) 500 expectNoError(err) 501 Expect(len(services)).To(Equal(3)) // 1 headless service for inter pod, 1 loadbalancer for 80:80 and 81:81, 1 node-port for 82 and 83 502 var headless, pub, np *corev1.Service 503 for _, svc := range services { 504 localSvc := svc 505 switch { 506 case strings.HasSuffix(localSvc.Name, "-published"): 507 pub = &localSvc 508 case strings.HasSuffix(localSvc.Name, "-random-ports"): 509 np = &localSvc 510 default: 511 headless = &localSvc 512 } 513 } 514 Expect(headless).NotTo(BeNil(), "Service web has no headless service!") 515 Expect(pub).NotTo(BeNil(), "Service web has no published service!") 516 Expect(np).NotTo(BeNil(), "Service web has no random ports service!") 517 Expect(len(pub.Spec.Ports)).To(Equal(2)) 518 Expect(pub.Spec.Type).To(Equal(corev1.ServiceType(*publishedServiceType))) 519 if corev1.ServiceType(*publishedServiceType) == corev1.ServiceTypeLoadBalancer { 520 Expect(pub.Spec.Ports[0].Port).To(Equal(int32(port0))) 521 Expect(pub.Spec.Ports[1].Port).To(Equal(int32(port1))) 522 523 } else { 524 Expect(pub.Spec.Ports[0].NodePort == int32(port0)).To(BeTrue()) 525 Expect(pub.Spec.Ports[1].NodePort == int32(port1)).To(BeTrue()) 526 } 527 528 Expect(pub.Spec.Ports[0].TargetPort).To(Equal(intstr.FromInt(80))) 529 Expect(pub.Spec.Ports[1].TargetPort).To(Equal(intstr.FromInt(81))) 530 Expect(len(np.Spec.Ports)).To(Equal(2)) 531 Expect(np.Spec.Ports[0].TargetPort).To(Equal(intstr.FromInt(82))) 532 Expect(np.Spec.Ports[1].TargetPort).To(Equal(intstr.FromInt(83))) 533 534 // check cleanup 535 expectNoError(ns.DeleteStack("app")) 536 waitUntil(ns.ServiceCount(stackServiceLabel("web"), 0)) 537 }) 538 539 It("Should support raw stack creation", func() { 540 kubeClient, err := kubernetes.NewForConfig(config) 541 expectNoError(err) 542 stackData := fmt.Sprintf(`{ 543 "apiVersion": "compose.docker.com/v1beta1", 544 "kind": "Stack", 545 "metadata": {"name": "app", "namespace": "%s"}, 546 "spec": { 547 "composeFile": "version: '3.2'\nservices:\n back:\n image: nginx:1.12.1-alpine" 548 } 549 }`, ns.Name()) 550 res := kubeClient.CoreV1().RESTClient().Verb("POST").RequestURI(fmt.Sprintf("/apis/compose.docker.com/v1beta1/namespaces/%s/stacks", ns.Name())).Body([]byte(stackData)).Do() 551 expectNoError(res.Error()) 552 waitUntil(ns.ContainsNPods(1)) 553 waitUntil(ns.IsStackAvailable("app")) 554 }) 555 556 It("Should create and update in v1beta1", func() { 557 port := getRandomPort() 558 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", fmt.Sprintf(`version: '3.2' 559 services: 560 front: 561 image: nginx:1.12.1-alpine 562 ports: 563 - %d:80`, port)) 564 expectNoError(err) 565 waitUntil(ns.IsStackAvailable("app")) 566 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 567 _, err = ns.UpdateStack(cluster.StackOperationV1beta1, "app", fmt.Sprintf(`version: '3.2' 568 services: 569 front: 570 image: httpd:2.4.27-alpine 571 ports: 572 - %d:80`, port)) 573 expectNoError(err) 574 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "It works!")) 575 }) 576 577 It("Should handle broken compose files", func() { 578 badspec := `version: '3.2' 579 services: 580 back: 581 image: nginx:1.12.1-alpine 582 bla: bli 583 blo: blo` 584 585 _, err := ns.CreateStack(defaultStrategy, "app", badspec) 586 Expect(err).To(HaveOccurred()) 587 }) 588 589 It("Should detect stack conflicts", func() { 590 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: '3.2' 591 services: 592 front: 593 image: nginx:1.12.1-alpine`) 594 expectNoError(err) 595 waitUntil(ns.IsStackAvailable("app")) 596 597 // create conflict 598 _, err = ns.CreateStack(cluster.StackOperationV1beta1, "app2", `version: '3.2' 599 services: 600 front: 601 image: nginx:1.12.1-alpine`) 602 Expect(err).To(HaveOccurred()) 603 604 _, err = ns.CreateStack(cluster.StackOperationV1beta1, "app2", `version: '3.2' 605 services: 606 front2: 607 image: nginx:1.12.1-alpine`) 608 expectNoError(err) 609 610 waitUntil(ns.IsStackAvailable("app2")) 611 // update conflict 612 _, err = ns.UpdateStack(cluster.StackOperationV1beta1, "app2", `version: '3.2' 613 services: 614 front: 615 image: nginx:1.12.1-alpine`) 616 Expect(err).To(HaveOccurred()) 617 618 _, err = ns.Deployments().Create(&appsv1.Deployment{ 619 ObjectMeta: metav1.ObjectMeta{ 620 Name: "front3", 621 Namespace: ns.Name(), 622 }, 623 Spec: appsv1.DeploymentSpec{ 624 Selector: &metav1.LabelSelector{ 625 MatchLabels: map[string]string{"app": "front3"}, 626 }, 627 Template: corev1.PodTemplateSpec{ 628 ObjectMeta: metav1.ObjectMeta{ 629 Labels: map[string]string{"app": "front3"}, 630 }, 631 Spec: corev1.PodSpec{ 632 Containers: []corev1.Container{ 633 { 634 Name: "front3-nginx", 635 Image: "nginx:1.12.1-alpine", 636 }, 637 }, 638 }, 639 }, 640 }, 641 }) 642 expectNoError(err) 643 _, err = ns.UpdateStack(cluster.StackOperationV1beta1, "app2", `version: '3.2' 644 services: 645 front3: 646 image: nginx:1.12.1-alpine`) 647 Expect(err).To(HaveOccurred()) 648 }) 649 650 It("Should read logs correctly v1beta2", func() { 651 port := getRandomPort() 652 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", fmt.Sprintf(`version: '3.2' 653 services: 654 front: 655 image: nginx:1.12.1-alpine 656 ports: 657 - %d:80`, port)) 658 expectNoError(err) 659 waitUntil(ns.IsStackAvailable("app")) 660 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 661 ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/nope.html", "nope") 662 s, err := ns.RESTClientV1beta2().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Stream() 663 expectNoError(err) 664 data, err := ioutil.ReadAll(s) 665 expectNoError(err) 666 s.Close() 667 sdata := string(data) 668 Expect(len(strings.Split(sdata, "\n"))).To(BeNumerically(">=", 2)) 669 Expect(strings.Contains(sdata, "GET")).To(BeTrue()) 670 // try with filter 671 s, err = ns.RESTClientV1beta2().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Param("filter", "404").Stream() 672 expectNoError(err) 673 data, err = ioutil.ReadAll(s) 674 expectNoError(err) 675 s.Close() 676 sdata = string(data) 677 Expect(len(strings.Split(sdata, "\n"))).To(Equal(1)) 678 }) 679 680 It("Should read logs correctly v1alpha3", func() { 681 port := getRandomPort() 682 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", fmt.Sprintf(`version: '3.2' 683 services: 684 front: 685 image: nginx:1.12.1-alpine 686 ports: 687 - %d:80`, port)) 688 expectNoError(err) 689 waitUntil(ns.IsStackAvailable("app")) 690 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 691 ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/nope.html", "nope") 692 s, err := ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Stream() 693 expectNoError(err) 694 data, err := ioutil.ReadAll(s) 695 expectNoError(err) 696 s.Close() 697 sdata := string(data) 698 Expect(len(strings.Split(sdata, "\n"))).To(BeNumerically(">=", 2)) 699 Expect(strings.Contains(sdata, "GET")).To(BeTrue()) 700 // try with filter 701 s, err = ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Param("filter", "404").Stream() 702 expectNoError(err) 703 data, err = ioutil.ReadAll(s) 704 expectNoError(err) 705 s.Close() 706 sdata = string(data) 707 Expect(len(strings.Split(sdata, "\n"))).To(Equal(1)) 708 }) 709 710 It("Should follow logs correctly v1beta2", func() { 711 port := getRandomPort() 712 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", fmt.Sprintf(`version: '3.2' 713 services: 714 front: 715 image: nginx:1.12.1-alpine 716 ports: 717 - %d:80`, port)) 718 expectNoError(err) 719 waitUntil(ns.IsStackAvailable("app")) 720 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 721 722 s, err := ns.RESTClientV1beta2().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Param("follow", "true").Stream() 723 expectNoError(err) 724 reader := bufio.NewReader(s) 725 lineStream := make(chan string, 100) 726 go func() { 727 for { 728 line, err := reader.ReadString('\n') 729 if err != nil { 730 lineStream <- "EOF" 731 break 732 } 733 lineStream <- line 734 } 735 }() 736 ok, _ := ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/notexisting.html", "nope")() 737 Expect(ok).To(BeFalse()) // 404 738 ok = drainUntil(lineStream, "notexisting.html") 739 Expect(ok).To(BeTrue()) 740 741 // shoot the pod 742 pods, err := ns.ListAllPods() 743 expectNoError(err) 744 Expect(len(pods)).To(Equal(1)) 745 originalPodName := pods[0].Name 746 ns.Pods().Delete(originalPodName, &metav1.DeleteOptions{}) 747 waitUntil(ns.PodIsActuallyRemoved(originalPodName)) 748 749 // check we get logs from the new pod 750 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 751 ok, _ = ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/notexisting2.html", "nope")() 752 Expect(ok).To(BeFalse()) // 404 753 ok = drainUntil(lineStream, "notexisting2.html") 754 Expect(ok).To(BeTrue()) 755 s.Close() 756 }) 757 758 It("Should follow logs correctly v1alpha3", func() { 759 port := getRandomPort() 760 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", fmt.Sprintf(`version: '3.2' 761 services: 762 front: 763 image: nginx:1.12.1-alpine 764 ports: 765 - %d:80`, port)) 766 expectNoError(err) 767 waitUntil(ns.IsStackAvailable("app")) 768 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 769 770 s, err := ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Param("follow", "true").Stream() 771 expectNoError(err) 772 reader := bufio.NewReader(s) 773 lineStream := make(chan string, 100) 774 go func() { 775 for { 776 line, err := reader.ReadString('\n') 777 if err != nil { 778 lineStream <- "EOF" 779 break 780 } 781 lineStream <- line 782 } 783 }() 784 ok, _ := ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/notexisting.html", "nope")() 785 Expect(ok).To(BeFalse()) // 404 786 ok = drainUntil(lineStream, "notexisting.html") 787 Expect(ok).To(BeTrue()) 788 789 // shoot the pod 790 pods, err := ns.ListAllPods() 791 expectNoError(err) 792 Expect(len(pods)).To(Equal(1)) 793 originalPodName := pods[0].Name 794 ns.Pods().Delete(originalPodName, &metav1.DeleteOptions{}) 795 waitUntil(ns.PodIsActuallyRemoved(originalPodName)) 796 797 // check we get logs from the new pod 798 waitUntil(ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/index.html", "Welcome to nginx!")) 799 ok, _ = ns.IsServiceResponding(fmt.Sprintf("front-published:%d-tcp", port), "proxy/notexisting2.html", "nope")() 800 Expect(ok).To(BeFalse()) // 404 801 ok = drainUntil(lineStream, "notexisting2.html") 802 Expect(ok).To(BeTrue()) 803 s.Close() 804 }) 805 806 It("Should fail fast when service name is invalid", func() { 807 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: '3.2' 808 services: 809 front_invalid: 810 image: nginx:1.12.1-alpine`) 811 Expect(err).To(HaveOccurred()) 812 }) 813 814 It("Should fail fast when volume name is invalid", func() { 815 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: '3.2' 816 services: 817 front: 818 image: nginx:1.12.1-alpine 819 volumes: 820 - data_invalid:/tmp`) 821 Expect(err).To(HaveOccurred()) 822 }) 823 824 It("Should fail fast when secret name is invalid", func() { 825 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: '3.2' 826 services: 827 front: 828 image: nginx:1.12.1-alpine 829 secrets: 830 - data_invalid 831 secrets: 832 data_invalid: 833 external: true`) 834 Expect(err).To(HaveOccurred()) 835 }) 836 837 It("Should fail on compose v2", func() { 838 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: "2" 839 services: 840 simple: 841 image: busybox:latest 842 command: top 843 another: 844 image: busybox:latest 845 command: top`) 846 Expect(err).To(HaveOccurred()) 847 Expect(err.Error()).To(ContainSubstring("unsupported Compose file version: 2")) 848 }) 849 850 It("Should fail on unallowed field", func() { 851 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: "3" 852 services: 853 simple: 854 image: busybox:latest 855 command: top 856 another: 857 image: busybox:latest 858 command: top 859 not_allowed_field: hello`) 860 Expect(err).To(HaveOccurred()) 861 Expect(err.Error()).To(ContainSubstring("Additional property not_allowed_field is not allowed")) 862 }) 863 864 It("Should fail on malformed yaml", func() { 865 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: "3" 866 services 867 simple: 868 image: busybox:latest 869 command: top 870 another: 871 image: busybox:latest 872 command: top`) 873 Expect(err).To(HaveOccurred()) 874 Expect(err.Error()).To(ContainSubstring("line 3: could not find expected ':'")) 875 }) 876 877 It("Should support bind volumes", func() { 878 _, err := ns.CreateStack(defaultStrategy, "app", `version: "3.4" 879 services: 880 test: 881 image: busybox:latest 882 command: 883 - /bin/sh 884 - -c 885 - "ls /tmp/hostetc/ ; sleep 3600" 886 volumes: 887 - type: bind 888 source: /etc 889 target: /tmp/hostetc`) 890 expectNoError(err) 891 waitUntil(ns.IsStackAvailable("app")) 892 s, err := ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Stream() 893 expectNoError(err) 894 defer s.Close() 895 data, err := ioutil.ReadAll(s) 896 expectNoError(err) 897 sdata := string(data) 898 Expect(strings.Contains(sdata, "hosts")).To(BeTrue()) 899 }) 900 901 It("Should support volumes", func() { 902 skipIfNoStorageClass(ns) 903 _, err := ns.CreateStack(defaultStrategy, "app", `version: "3.4" 904 services: 905 test: 906 image: busybox:latest 907 command: 908 - /bin/sh 909 - -c 910 - "touch /tmp/mountvolume/somefile && echo success ; sleep 3600" 911 volumes: 912 - myvolume:/tmp/mountvolume 913 volumes: 914 myvolume:`) 915 expectNoError(err) 916 waitUntil(ns.IsStackAvailable("app")) 917 s, err := ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Stream() 918 expectNoError(err) 919 defer s.Close() 920 data, err := ioutil.ReadAll(s) 921 expectNoError(err) 922 sdata := string(data) 923 Expect(strings.Contains(sdata, "success")).To(BeTrue()) 924 }) 925 926 It("Should support anonymous volumes", func() { 927 skipIfNoStorageClass(ns) 928 _, err := ns.CreateStack(defaultStrategy, "app", `version: "3.4" 929 services: 930 test: 931 image: busybox:latest 932 command: 933 - /bin/sh 934 - -c 935 - "touch /tmp/mountvolume/somefile && echo success ; sleep 3600" 936 volumes: 937 - /tmp/mountvolume`) 938 expectNoError(err) 939 waitUntil(ns.IsStackAvailable("app")) 940 s, err := ns.RESTClientV1alpha3().Get().Namespace(ns.Name()).Name("app").Resource("stacks").SubResource("log").Stream() 941 expectNoError(err) 942 defer s.Close() 943 data, err := ioutil.ReadAll(s) 944 expectNoError(err) 945 sdata := string(data) 946 Expect(strings.Contains(sdata, "success")).To(BeTrue()) 947 }) 948 949 It("Should survive a yaml bomb", func() { 950 spec := ` 951 version: "3" 952 services: &services ["lol","lol","lol","lol","lol","lol","lol","lol","lol"] 953 b: &b [*services,*services,*services,*services,*services,*services,*services,*services,*services] 954 c: &c [*b,*b,*b,*b,*b,*b,*b,*b,*b] 955 d: &d [*c,*c,*c,*c,*c,*c,*c,*c,*c] 956 e: &e [*d,*d,*d,*d,*d,*d,*d,*d,*d] 957 f: &f [*e,*e,*e,*e,*e,*e,*e,*e,*e] 958 g: &g [*f,*f,*f,*f,*f,*f,*f,*f,*f] 959 h: &h [*g,*g,*g,*g,*g,*g,*g,*g,*g] 960 i: &i [*h,*h,*h,*h,*h,*h,*h,*h,*h]` 961 _, err := ns.CreateStack(cluster.StackOperationV1beta1, "app", spec) 962 Expect(err).To(HaveOccurred()) 963 _, err = ns.CreateStack(cluster.StackOperationV1beta1, "app", `version: '3.2' 964 services: 965 front: 966 image: nginx:1.12.1-alpine`) 967 Expect(err).NotTo(HaveOccurred()) 968 _, err = ns.UpdateStack(cluster.StackOperationV1beta1, "app", spec) 969 Expect(err).To(HaveOccurred()) 970 _, err = ns.UpdateStack(cluster.StackOperationV1beta1, "app", `version: '3.2' 971 services: 972 front: 973 image: nginx:1.12.1-alpine`) 974 Expect(err).NotTo(HaveOccurred()) 975 }) 976 It("Should update status of invalid stacks", func() { 977 kcli := ns.StacksV1beta1() 978 stack := &v1beta1.Stack{ 979 ObjectMeta: metav1.ObjectMeta{ 980 Name: "invalid-mount", 981 }, 982 Spec: v1beta1.StackSpec{ 983 ComposeFile: `version: "3.3" 984 services: 985 mounts: 986 image: nginx 987 volumes: 988 - "./web/static:/static"`, 989 }, 990 } 991 _, err := kcli.WithSkipValidation().Create(stack) 992 Expect(err).ToNot(HaveOccurred()) 993 waitUntil(ns.IsStackFailed("invalid-mount", "web/static: only absolute paths can be specified in mount source")) 994 }) 995 It("Should update status stacks with invalid yaml", func() { 996 kcli := ns.StacksV1beta1() 997 stack := &v1beta1.Stack{ 998 ObjectMeta: metav1.ObjectMeta{ 999 Name: "invalid-yaml", 1000 }, 1001 Spec: v1beta1.StackSpec{ 1002 ComposeFile: `version: "3.3" 1003 invalid-yaml"`, 1004 }, 1005 } 1006 _, err := kcli.WithSkipValidation().Create(stack) 1007 Expect(err).ToNot(HaveOccurred()) 1008 waitUntil(ns.IsStackFailed("invalid-yaml", "parsing error")) 1009 }) 1010 1011 It("Should remove owned configs and secrets", func() { 1012 _, err := ns.CreateStack(cluster.StackOperationV1beta2Stack, "app", `version: '3.3' 1013 services: 1014 front: 1015 image: nginx:1.12.1-alpine 1016 secrets: 1017 test-secret: 1018 file: ./secret-data 1019 configs: 1020 test-config: 1021 file: ./config-data`) 1022 Expect(err).ToNot(HaveOccurred()) 1023 _, err = ns.ConfigMaps().Create(&corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "test-config", Labels: map[string]string{labels.ForStackName: "app"}}}) 1024 Expect(err).ToNot(HaveOccurred()) 1025 _, err = ns.Secrets().Create(&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Labels: map[string]string{labels.ForStackName: "app"}}}) 1026 Expect(err).ToNot(HaveOccurred()) 1027 waitUntil(ns.IsStackAvailable("app")) 1028 Expect(ns.DeleteStack("app")).ToNot(HaveOccurred()) 1029 waitUntil(func() (done bool, err error) { 1030 _, err = ns.ConfigMaps().Get("test-config", metav1.GetOptions{}) 1031 if apierrors.IsNotFound(err) { 1032 return true, nil 1033 } 1034 return false, err 1035 }) 1036 waitUntil(func() (done bool, err error) { 1037 _, err = ns.Secrets().Get("test-secret", metav1.GetOptions{}) 1038 if apierrors.IsNotFound(err) { 1039 return true, nil 1040 } 1041 return false, err 1042 }) 1043 }) 1044 1045 It("Should deploy stacks with private images", func() { 1046 err := ns.CreatePullSecret("test-pull-secret", "https://index.docker.io/v1/", privateImagePullUsername, privateImagePullPassword) 1047 expectNoError(err) 1048 s := &latest.Stack{ 1049 ObjectMeta: metav1.ObjectMeta{ 1050 Name: "with-private-images", 1051 }, 1052 Spec: &latest.StackSpec{ 1053 Services: []latest.ServiceConfig{ 1054 { 1055 Name: "test-service", 1056 Image: privateImagePullImage, 1057 PullSecret: "test-pull-secret", 1058 }, 1059 }, 1060 }, 1061 } 1062 s, err = ns.StacksV1alpha3().Create(s) 1063 expectNoError(err) 1064 waitUntil(ns.IsStackAvailable(s.Name)) 1065 }) 1066 It("Should delete stacks with propagation=Foreground", func() { 1067 _, err := ns.CreateStack(cluster.StackOperationV1alpha3, "app", `version: '3.2' 1068 services: 1069 front: 1070 image: nginx:1.12.1-alpine`) 1071 Expect(err).NotTo(HaveOccurred()) 1072 waitUntil(ns.IsStackAvailable("app")) 1073 err = ns.DeleteStackWithPropagation("app", metav1.DeletePropagationForeground) 1074 Expect(err).NotTo(HaveOccurred()) 1075 waitUntil(ns.ContainsZeroStack()) 1076 }) 1077 1078 It("Should leverage cluster IP if InternalPorts are specified", func() { 1079 stack, err := ns.CreateStack(cluster.StackOperationV1alpha3, "app", `version: '3.2' 1080 services: 1081 front: 1082 image: nginx:1.12.1-alpine`) 1083 Expect(err).NotTo(HaveOccurred()) 1084 waitUntil(ns.IsStackAvailable("app")) 1085 svc, err := ns.Services().Get("front", metav1.GetOptions{}) 1086 Expect(err).NotTo(HaveOccurred()) 1087 Expect(svc.Spec.ClusterIP).To(Equal(corev1.ClusterIPNone)) 1088 stack.Spec.Services[0].InternalPorts = []latest.InternalPort{ 1089 { 1090 Port: 80, 1091 Protocol: corev1.ProtocolTCP, 1092 }, 1093 } 1094 stack, err = ns.UpdateStackFromSpec("app", stack) 1095 Expect(err).NotTo(HaveOccurred()) 1096 waitUntil(ns.IsStackAvailable("app")) 1097 svc, err = ns.Services().Get("front", metav1.GetOptions{}) 1098 Expect(err).NotTo(HaveOccurred()) 1099 Expect(svc.Spec.ClusterIP).NotTo(Equal(corev1.ClusterIPNone)) 1100 stack.Spec.Services[0].InternalPorts = nil 1101 stack, err = ns.UpdateStackFromSpec("app", stack) 1102 Expect(err).NotTo(HaveOccurred()) 1103 waitUntil(ns.IsStackAvailable("app")) 1104 svc, err = ns.Services().Get("front", metav1.GetOptions{}) 1105 Expect(err).NotTo(HaveOccurred()) 1106 Expect(svc.Spec.ClusterIP).To(Equal(corev1.ClusterIPNone)) 1107 }) 1108 1109 }) 1110 1111 func drainUntil(stream chan string, match string) bool { 1112 to := time.After(30 * time.Second) 1113 for { 1114 select { 1115 case line := <-stream: 1116 if strings.Contains(line, match) { 1117 return true 1118 } 1119 case <-to: 1120 return false 1121 } 1122 } 1123 } 1124 1125 // helpers 1126 1127 func createNamespace() (*cluster.Namespace, func()) { 1128 namespaceName := strings.ToLower(fmt.Sprintf("%s-%s-%d", deployNamespace, "compose", rand.Int63())) 1129 1130 ns, cleanup, err := cluster.CreateNamespace(config, config, namespaceName) 1131 if apierrors.IsAlreadyExists(err) { 1132 // retry with another "random" namespace name 1133 return createNamespace() 1134 } 1135 expectNoError(err) 1136 1137 return ns, cleanup 1138 } 1139 1140 func expectNoError(err error) { 1141 ExpectWithOffset(1, err).NotTo(HaveOccurred()) 1142 } 1143 1144 func waitUntil(condition wait.ConditionFunc) { 1145 ExpectWithOffset(1, wait.PollImmediate(1*time.Second, 10*time.Minute, condition)).NotTo(HaveOccurred()) 1146 } 1147 1148 func stackServiceLabel(name string) string { 1149 return "com.docker.service.name=" + name 1150 }