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  }