github.com/deiscc/workflow-e2e@v0.0.0-20181208071258-117299af888f/tests/ps_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"net/http"
     7  	"regexp"
     8  	"sort"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/deiscc/workflow-e2e/tests/cmd"
    13  	"github.com/deiscc/workflow-e2e/tests/cmd/apps"
    14  	"github.com/deiscc/workflow-e2e/tests/cmd/auth"
    15  	"github.com/deiscc/workflow-e2e/tests/cmd/builds"
    16  	"github.com/deiscc/workflow-e2e/tests/model"
    17  	"github.com/deiscc/workflow-e2e/tests/settings"
    18  
    19  	. "github.com/onsi/ginkgo"
    20  	. "github.com/onsi/ginkgo/extensions/table"
    21  	. "github.com/onsi/gomega"
    22  	. "github.com/onsi/gomega/gbytes"
    23  	. "github.com/onsi/gomega/gexec"
    24  )
    25  
    26  var _ = Describe("deis ps", func() {
    27  
    28  	Context("with an existing user", func() {
    29  
    30  		var user model.User
    31  
    32  		BeforeEach(func() {
    33  			user = auth.RegisterAndLogin()
    34  		})
    35  
    36  		AfterEach(func() {
    37  			auth.Cancel(user)
    38  		})
    39  
    40  		Context("who owns an existing app that has already been deployed", func() {
    41  
    42  			var app model.App
    43  
    44  			BeforeEach(func() {
    45  				app = apps.Create(user, "--no-remote")
    46  				builds.Create(user, app)
    47  			})
    48  
    49  			AfterEach(func() {
    50  				apps.Destroy(user, app)
    51  			})
    52  
    53  			DescribeTable("that user can scale that app up and down",
    54  				func(scaleTo, respCode int) {
    55  					sess, err := cmd.Start("deis ps:scale cmd=%d --app=%s", &user, scaleTo, app.Name)
    56  					Eventually(sess).Should(Say("Scaling processes... but first,"))
    57  					Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`))
    58  					Eventually(sess).Should(Say("=== %s Processes", app.Name))
    59  					Expect(err).NotTo(HaveOccurred())
    60  					Eventually(sess).Should(Exit(0))
    61  
    62  					// test that there are the right number of processes listed
    63  					procsListing := listProcs(user, app, "").Out.Contents()
    64  					procs := scrapeProcs(app.Name, procsListing)
    65  					Expect(procs).To(HaveLen(scaleTo))
    66  
    67  					// curl the app's root URL and print just the HTTP response code
    68  					cmdRetryTimeout := 60
    69  					curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)}
    70  					Eventually(cmd.Retry(curlCmd, strconv.Itoa(respCode), cmdRetryTimeout)).Should(BeTrue())
    71  				},
    72  				Entry("scales to 1", 1, 200),
    73  				Entry("scales to 3", 3, 200),
    74  				Entry("scales to 0", 0, 503),
    75  			)
    76  
    77  			DescribeTable("that user can interrupt a scaling event",
    78  				func(scaleTo, respCode int) {
    79  
    80  					sess, err := cmd.Start("deis ps:scale cmd=%d --app=%s", &user, scaleTo, app.Name)
    81  					Eventually(sess).Should(Say("Scaling processes... but first,"))
    82  
    83  					Expect(err).NotTo(HaveOccurred())
    84  
    85  					// Sleep for a split second to ensure scale command makes it to the server.
    86  					time.Sleep(200 * time.Millisecond)
    87  
    88  					// Interrupt and wait for exit.
    89  					sess = sess.Interrupt().Wait()
    90  
    91  					// Ensure the right number of processes listed.
    92  					Eventually(func() []string {
    93  						procsListing := listProcs(user, app, "").Out.Contents()
    94  						return scrapeProcs(app.Name, procsListing)
    95  					}, settings.MaxEventuallyTimeout).Should(HaveLen(scaleTo))
    96  
    97  					// curl the app's root URL and print just the HTTP response code
    98  					cmdRetryTimeout := 60
    99  					curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)}
   100  					Eventually(cmd.Retry(curlCmd, strconv.Itoa(respCode), cmdRetryTimeout)).Should(BeTrue())
   101  				},
   102  				Entry("scales to 3", 3, 200),
   103  				Entry("scales to 0", 0, 503),
   104  			)
   105  
   106  			// TODO: Test is broken
   107  			XIt("that app remains responsive during a scaling event", func() {
   108  				stopCh := make(chan struct{})
   109  				doneCh := make(chan struct{})
   110  
   111  				// start scaling the app
   112  				go func() {
   113  					for range stopCh {
   114  						sess, err := cmd.Start("deis ps:scale web=4 -a %s", &user, app.Name)
   115  						Eventually(sess).Should(Exit(0))
   116  						Expect(err).NotTo(HaveOccurred())
   117  					}
   118  					close(doneCh)
   119  				}()
   120  
   121  				for i := 0; i < 10; i++ {
   122  					// start the scale operation. waits until the last scale op has finished
   123  					stopCh <- struct{}{}
   124  					resp, err := http.Get(app.URL)
   125  					Expect(err).To(BeNil())
   126  					Expect(resp.StatusCode).To(BeEquivalentTo(http.StatusOK))
   127  				}
   128  
   129  				// wait until the goroutine that was scaling the app shuts down. not strictly necessary, just good practice
   130  				Eventually(doneCh).Should(BeClosed())
   131  			})
   132  
   133  			DescribeTable("that user can restart that app's processes",
   134  				func(restart string, scaleTo int, respCode int) {
   135  					// TODO: need some way to choose between "web" and "cmd" here!
   136  					// scale the app's processes to the desired number
   137  					sess, err := cmd.Start("deis ps:scale cmd=%d --app=%s", &user, scaleTo, app.Name)
   138  
   139  					Eventually(sess).Should(Say("Scaling processes... but first,"))
   140  					Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`))
   141  					Eventually(sess).Should(Say("=== %s Processes", app.Name))
   142  					Expect(err).NotTo(HaveOccurred())
   143  					Eventually(sess).Should(Exit(0))
   144  
   145  					// capture the process names
   146  					beforeProcs := scrapeProcs(app.Name, sess.Out.Contents())
   147  
   148  					// restart the app's process(es)
   149  					var arg string
   150  					switch restart {
   151  					case "all":
   152  						arg = ""
   153  					case "by type":
   154  						// TODO: need some way to choose between "web" and "cmd" here!
   155  						arg = "cmd"
   156  					case "by wrong type":
   157  						// TODO: need some way to choose between "web" and "cmd" here!
   158  						arg = "web"
   159  					case "one":
   160  						procsLen := len(beforeProcs)
   161  						Expect(procsLen).To(BeNumerically(">", 0))
   162  						arg = beforeProcs[rand.Intn(procsLen)]
   163  					}
   164  					sess, err = cmd.Start("deis ps:restart %s --app=%s", &user, arg, app.Name)
   165  					Eventually(sess).Should(Say("Restarting processes... but first,"))
   166  					if scaleTo == 0 || restart == "by wrong type" {
   167  						Eventually(sess, settings.MaxEventuallyTimeout).Should(Say("Could not find any processes to restart"))
   168  					} else {
   169  						Eventually(sess, settings.MaxEventuallyTimeout).Should(Say(`done in \d+s`))
   170  						Eventually(sess).Should(Say("=== %s Processes", app.Name))
   171  					}
   172  					Expect(err).NotTo(HaveOccurred())
   173  					Eventually(sess).Should(Exit(0))
   174  
   175  					// capture the process names
   176  					procsListing := listProcs(user, app, "").Out.Contents()
   177  					afterProcs := scrapeProcs(app.Name, procsListing)
   178  
   179  					// compare the before and after sets of process names
   180  					Expect(afterProcs).To(HaveLen(scaleTo))
   181  					if scaleTo > 0 && restart != "by wrong type" {
   182  						Expect(beforeProcs).NotTo(Equal(afterProcs))
   183  					}
   184  
   185  					// curl the app's root URL and print just the HTTP response code
   186  					cmdRetryTimeout := 60
   187  					curlCmd := model.Cmd{CommandLineString: fmt.Sprintf(`curl -sL -w "%%{http_code}\\n" "%s" -o /dev/null`, app.URL)}
   188  					Eventually(cmd.Retry(curlCmd, strconv.Itoa(respCode), cmdRetryTimeout)).Should(BeTrue())
   189  				},
   190  				Entry("restarts one of 1", "one", 1, 200),
   191  				Entry("restarts all of 1", "all", 1, 200),
   192  				Entry("restarts all of 1 by type", "by type", 1, 200),
   193  				Entry("restarts all of 1 by wrong type", "by wrong type", 1, 200),
   194  				Entry("restarts one of 3", "one", 3, 200),
   195  				Entry("restarts all of 3", "all", 3, 200),
   196  				Entry("restarts all of 3 by type", "by type", 3, 200),
   197  				Entry("restarts all of 3 by wrong type", "by wrong type", 3, 200),
   198  				Entry("restarts all of 0", "all", 0, 503),
   199  				Entry("restarts all of 0 by type", "by type", 0, 503),
   200  				Entry("restarts all of 0 by wrong type", "by wrong type", 0, 503),
   201  			)
   202  
   203  		})
   204  
   205  	})
   206  
   207  })
   208  
   209  func listProcs(user model.User, app model.App, proctype string) *Session {
   210  	sess, err := cmd.Start("deis ps:list --app=%s", &user, app.Name)
   211  	Eventually(sess).Should(Say("=== %s Processes", app.Name))
   212  	if proctype != "" {
   213  		Eventually(sess).Should(Say("--- %s:", proctype))
   214  	}
   215  	Expect(err).NotTo(HaveOccurred())
   216  	Eventually(sess).Should(Exit(0))
   217  	return sess
   218  }
   219  
   220  // scrapeProcs returns the sorted process names for an app from the given output.
   221  // It matches the current "deis ps" output for a healthy container:
   222  //   earthy-vocalist-cmd-123456789-1d73e up (v2)
   223  //   myapp-web-123456789-bujlq up (v16)
   224  func scrapeProcs(app string, output []byte) []string {
   225  	procsRegexp := `(%s-[\w-]+) up \(v\d+\)`
   226  	re := regexp.MustCompile(fmt.Sprintf(procsRegexp, app))
   227  	found := re.FindAllSubmatch(output, -1)
   228  	procs := make([]string, len(found))
   229  	for i := range found {
   230  		procs[i] = string(found[i][1])
   231  	}
   232  	sort.Strings(procs)
   233  	return procs
   234  }