github.com/deis/workflow-e2e@v2.12.2-0.20180227201524-4105be7001fe+incompatible/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/deis/workflow-e2e/tests/cmd" 13 "github.com/deis/workflow-e2e/tests/cmd/apps" 14 "github.com/deis/workflow-e2e/tests/cmd/auth" 15 "github.com/deis/workflow-e2e/tests/cmd/builds" 16 "github.com/deis/workflow-e2e/tests/model" 17 "github.com/deis/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 }