github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/integration/experimental/v3_scale_command_test.go (about) 1 package experimental 2 3 import ( 4 "fmt" 5 "strings" 6 7 "code.cloudfoundry.org/cli/integration/helpers" 8 . "github.com/onsi/ginkgo" 9 . "github.com/onsi/gomega" 10 . "github.com/onsi/gomega/gbytes" 11 . "github.com/onsi/gomega/gexec" 12 . "github.com/onsi/gomega/ghttp" 13 ) 14 15 var _ = Describe("v3-scale command", func() { 16 var ( 17 orgName string 18 spaceName string 19 appName string 20 userName string 21 ) 22 23 BeforeEach(func() { 24 orgName = helpers.NewOrgName() 25 spaceName = helpers.NewSpaceName() 26 appName = helpers.PrefixedRandomName("app") 27 userName, _ = helpers.GetCredentials() 28 }) 29 30 Describe("help", func() { 31 Context("when --help flag is set", func() { 32 It("displays command usage to output", func() { 33 session := helpers.CF("v3-scale", "--help") 34 35 Eventually(session).Should(Say("NAME:")) 36 Eventually(session).Should(Say("v3-scale - Change or view the instance count, disk space limit, and memory limit for an app")) 37 38 Eventually(session).Should(Say("USAGE:")) 39 Eventually(session).Should(Say("cf v3-scale APP_NAME \\[--process PROCESS\\] \\[-i INSTANCES\\] \\[-k DISK\\] \\[-m MEMORY\\]")) 40 41 Eventually(session).Should(Say("OPTIONS:")) 42 Eventually(session).Should(Say("-f\\s+Force restart of app without prompt")) 43 Eventually(session).Should(Say("-i\\s+Number of instances")) 44 Eventually(session).Should(Say("-k\\s+Disk limit \\(e\\.g\\. 256M, 1024M, 1G\\)")) 45 Eventually(session).Should(Say("-m\\s+Memory limit \\(e\\.g\\. 256M, 1024M, 1G\\)")) 46 Eventually(session).Should(Say("--process\\s+App process to scale \\(Default: web\\)")) 47 48 Eventually(session).Should(Say("ENVIRONMENT:")) 49 Eventually(session).Should(Say("CF_STARTUP_TIMEOUT=5\\s+Max wait time for app instance startup, in minutes")) 50 51 Eventually(session).Should(Exit(0)) 52 }) 53 }) 54 }) 55 56 It("displays the experimental warning", func() { 57 session := helpers.CF("v3-scale", appName) 58 Eventually(session).Should(Say("This command is in EXPERIMENTAL stage and may change without notice")) 59 Eventually(session).Should(Exit()) 60 }) 61 62 Context("when the environment is not setup correctly", func() { 63 Context("when no API endpoint is set", func() { 64 BeforeEach(func() { 65 helpers.UnsetAPI() 66 }) 67 68 It("fails with no API endpoint set message", func() { 69 session := helpers.CF("v3-scale", appName) 70 Eventually(session).Should(Say("FAILED")) 71 Eventually(session.Err).Should(Say("No API endpoint set\\. Use 'cf login' or 'cf api' to target an endpoint\\.")) 72 Eventually(session).Should(Exit(1)) 73 }) 74 }) 75 76 Context("when the v3 api does not exist", func() { 77 var server *Server 78 79 BeforeEach(func() { 80 server = helpers.StartAndTargetServerWithoutV3API() 81 }) 82 83 AfterEach(func() { 84 server.Close() 85 }) 86 87 It("fails with error message that the minimum version is not met", func() { 88 session := helpers.CF("v3-scale", appName) 89 Eventually(session).Should(Say("FAILED")) 90 Eventually(session.Err).Should(Say("This command requires CF API version 3\\.27\\.0 or higher\\.")) 91 Eventually(session).Should(Exit(1)) 92 }) 93 }) 94 95 Context("when the v3 api version is lower than the minimum version", func() { 96 var server *Server 97 98 BeforeEach(func() { 99 server = helpers.StartAndTargetServerWithV3Version("3.0.0") 100 }) 101 102 AfterEach(func() { 103 server.Close() 104 }) 105 106 It("fails with error message that the minimum version is not met", func() { 107 session := helpers.CF("v3-scale", appName) 108 Eventually(session).Should(Say("FAILED")) 109 Eventually(session.Err).Should(Say("This command requires CF API version 3\\.27\\.0 or higher\\.")) 110 Eventually(session).Should(Exit(1)) 111 }) 112 }) 113 114 Context("when not logged in", func() { 115 BeforeEach(func() { 116 helpers.LogoutCF() 117 }) 118 119 It("fails with not logged in message", func() { 120 session := helpers.CF("v3-scale", appName) 121 Eventually(session).Should(Say("FAILED")) 122 Eventually(session.Err).Should(Say("Not logged in\\. Use 'cf login' to log in\\.")) 123 Eventually(session).Should(Exit(1)) 124 }) 125 }) 126 127 Context("when there is no org set", func() { 128 BeforeEach(func() { 129 helpers.LogoutCF() 130 helpers.LoginCF() 131 }) 132 133 It("fails with no org targeted error message", func() { 134 session := helpers.CF("v3-scale", appName) 135 Eventually(session).Should(Say("FAILED")) 136 Eventually(session.Err).Should(Say("No org targeted, use 'cf target -o ORG' to target an org\\.")) 137 Eventually(session).Should(Exit(1)) 138 }) 139 }) 140 141 Context("when there is no space set", func() { 142 BeforeEach(func() { 143 helpers.LogoutCF() 144 helpers.LoginCF() 145 helpers.TargetOrg(ReadOnlyOrg) 146 }) 147 148 It("fails with no space targeted error message", func() { 149 session := helpers.CF("v3-scale", appName) 150 Eventually(session).Should(Say("FAILED")) 151 Eventually(session.Err).Should(Say("No space targeted, use 'cf target -s SPACE' to target a space\\.")) 152 Eventually(session).Should(Exit(1)) 153 }) 154 }) 155 }) 156 157 Context("when the environment is set up correctly", func() { 158 BeforeEach(func() { 159 setupCF(orgName, spaceName) 160 }) 161 162 AfterEach(func() { 163 helpers.QuickDeleteOrg(orgName) 164 }) 165 166 Context("when the app name is not provided", func() { 167 It("tells the user that the app name is required, prints help text, and exits 1", func() { 168 session := helpers.CF("v3-scale") 169 170 Eventually(session.Err).Should(Say("Incorrect Usage: the required argument `APP_NAME` was not provided")) 171 Eventually(session).Should(Say("NAME:")) 172 Eventually(session).Should(Exit(1)) 173 }) 174 }) 175 176 Context("when the app does not exist", func() { 177 It("displays app not found and exits 1", func() { 178 invalidAppName := "invalid-app-name" 179 session := helpers.CF("v3-scale", invalidAppName) 180 Eventually(session.Err).Should(Say("App %s not found", invalidAppName)) 181 Eventually(session).Should(Say("FAILED")) 182 Eventually(session).Should(Exit(1)) 183 }) 184 }) 185 186 Context("when the app exists", func() { 187 BeforeEach(func() { 188 helpers.WithProcfileApp(func(appDir string) { 189 Eventually(helpers.CustomCF(helpers.CFEnv{WorkingDirectory: appDir}, "v3-push", appName)).Should(Exit(0)) 190 }) 191 }) 192 193 Context("when scale option flags are not provided", func() { 194 It("displays the current scale properties for all processes", func() { 195 session := helpers.CF("v3-scale", appName) 196 197 Eventually(session).Should(Say("Showing current scale of app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 198 Consistently(session).ShouldNot(Say("Scaling")) 199 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 200 Consistently(session).ShouldNot(Say("Stopping")) 201 Consistently(session).ShouldNot(Say("Starting")) 202 Consistently(session).ShouldNot(Say("Waiting")) 203 Eventually(session).Should(Exit(0)) 204 205 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 206 Expect(len(appTable.Processes)).To(Equal(3)) 207 208 processSummary := appTable.Processes[0] 209 Expect(processSummary.Title).To(Equal("web:1/1")) 210 211 instanceSummary := processSummary.Instances[0] 212 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 213 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 214 215 Expect(appTable.Processes[1].Title).To(Equal("console:0/0")) 216 Expect(appTable.Processes[2].Title).To(Equal("rake:0/0")) 217 }) 218 }) 219 220 Context("when only one scale option flag is provided", func() { 221 It("scales the app accordingly", func() { 222 By("verifying we start with a single instance") 223 session := helpers.CF("v3-scale", appName) 224 Eventually(session).Should(Exit(0)) 225 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 226 Expect(appTable.Processes).To(HaveLen(3)) 227 228 By("scaling to 3 instances") 229 session = helpers.CF("v3-scale", appName, "-i", "3") 230 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 231 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 232 Consistently(session).ShouldNot(Say("Stopping")) 233 Consistently(session).ShouldNot(Say("Starting")) 234 Eventually(session).Should(Exit(0)) 235 236 updatedAppTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 237 Expect(updatedAppTable.Processes).To(HaveLen(3)) 238 239 processSummary := updatedAppTable.Processes[0] 240 instanceSummary := processSummary.Instances[0] 241 Expect(processSummary.Title).To(MatchRegexp(`web:\d/3`)) 242 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 243 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 244 245 By("scaling memory to 64M") 246 buffer := NewBuffer() 247 buffer.Write([]byte("y\n")) 248 session = helpers.CFWithStdin(buffer, "v3-scale", appName, "-m", "64M") 249 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 250 Eventually(session).Should(Say("This will cause the app to restart\\. Are you sure you want to scale %s\\? \\[yN\\]:", appName)) 251 Eventually(session).Should(Say("Stopping app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 252 Eventually(session).Should(Say("Starting app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 253 Eventually(session).Should(Exit(0)) 254 255 updatedAppTable = helpers.ParseV3AppProcessTable(session.Out.Contents()) 256 Expect(updatedAppTable.Processes).To(HaveLen(3)) 257 258 processSummary = updatedAppTable.Processes[0] 259 instanceSummary = processSummary.Instances[0] 260 Expect(processSummary.Title).To(MatchRegexp(`web:\d/3`)) 261 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 64M`)) 262 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 263 264 By("scaling disk to 92M") 265 buffer = NewBuffer() 266 buffer.Write([]byte("y\n")) 267 session = helpers.CFWithStdin(buffer, "v3-scale", appName, "-k", "92M") 268 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 269 Eventually(session).Should(Say("This will cause the app to restart\\. Are you sure you want to scale %s\\? \\[yN\\]:", appName)) 270 Eventually(session).Should(Say("Stopping app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 271 Eventually(session).Should(Say("Starting app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 272 Eventually(session).Should(Exit(0)) 273 274 updatedAppTable = helpers.ParseV3AppProcessTable(session.Out.Contents()) 275 Expect(updatedAppTable.Processes).To(HaveLen(3)) 276 277 processSummary = updatedAppTable.Processes[0] 278 instanceSummary = processSummary.Instances[0] 279 Expect(processSummary.Title).To(MatchRegexp(`web:\d/3`)) 280 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 64M`)) 281 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 92M`)) 282 283 By("scaling to 0 instances") 284 session = helpers.CF("v3-scale", appName, "-i", "0") 285 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 286 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 287 Consistently(session).ShouldNot(Say("Stopping")) 288 Consistently(session).ShouldNot(Say("Starting")) 289 Eventually(session).Should(Exit(0)) 290 291 updatedAppTable = helpers.ParseV3AppProcessTable(session.Out.Contents()) 292 Expect(updatedAppTable.Processes).To(BeEmpty()) 293 }) 294 295 Context("when the user chooses not to restart the app", func() { 296 It("cancels the scale", func() { 297 buffer := NewBuffer() 298 buffer.Write([]byte("n\n")) 299 session := helpers.CFWithStdin(buffer, "v3-scale", appName, "-i", "2", "-k", "90M") 300 Eventually(session).Should(Say("This will cause the app to restart")) 301 Consistently(session).ShouldNot(Say("Stopping")) 302 Consistently(session).ShouldNot(Say("Starting")) 303 Eventually(session).Should(Say("Scaling cancelled")) 304 Consistently(session).ShouldNot(Say("Waiting for app to start\\.\\.\\.")) 305 Eventually(session).Should(Exit(0)) 306 307 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 308 Expect(appTable.Processes).To(BeEmpty()) 309 }) 310 }) 311 }) 312 313 Context("when all scale option flags are provided", func() { 314 Context("when the app starts successfully", func() { 315 It("scales the app accordingly", func() { 316 buffer := NewBuffer() 317 buffer.Write([]byte("y\n")) 318 session := helpers.CFWithStdin(buffer, "v3-scale", appName, "-i", "2", "-k", "120M", "-m", "60M") 319 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 320 Eventually(session).Should(Say("This will cause the app to restart\\. Are you sure you want to scale %s\\? \\[yN\\]:", appName)) 321 Eventually(session).Should(Say("Stopping app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 322 Eventually(session).Should(Say("Starting app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 323 Eventually(session).Should(Exit(0)) 324 325 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 326 Expect(appTable.Processes).To(HaveLen(3)) 327 328 processSummary := appTable.Processes[0] 329 instanceSummary := processSummary.Instances[0] 330 Expect(processSummary.Title).To(MatchRegexp(`web:\d/2`)) 331 Expect(instanceSummary.State).To(MatchRegexp(`running|starting`)) 332 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 60M`)) 333 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 120M`)) 334 }) 335 }) 336 337 Context("when the app does not start successfully", func() { 338 It("scales the app and displays the app summary", func() { 339 buffer := NewBuffer() 340 buffer.Write([]byte("y\n")) 341 session := helpers.CFWithStdin(buffer, "v3-scale", appName, "-i", "2", "-k", "120M", "-m", "6M") 342 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 343 Eventually(session).Should(Say("This will cause the app to restart\\. Are you sure you want to scale %s\\? \\[yN\\]:", appName)) 344 Eventually(session).Should(Say("Stopping app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 345 Eventually(session).Should(Say("Starting app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 346 Eventually(session).Should(Exit(0)) 347 348 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 349 Expect(appTable.Processes).To(HaveLen(3)) 350 351 processSummary := appTable.Processes[0] 352 instanceSummary := processSummary.Instances[0] 353 Expect(processSummary.Title).To(MatchRegexp(`web:\d/2`)) 354 Expect(instanceSummary.State).To(MatchRegexp(`crashed`)) 355 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 6M`)) 356 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of 120M`)) 357 }) 358 }) 359 }) 360 361 PContext("when the provided scale options are the same as the existing scale properties", func() { 362 var ( 363 session *Session 364 currentInstances string 365 maxMemory string 366 maxDiskSize string 367 ) 368 369 BeforeEach(func() { 370 session = helpers.CF("v3-scale", appName) 371 Eventually(session).Should(Exit(0)) 372 373 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 374 instanceSummary := appTable.Processes[0].Instances[0] 375 currentInstances = string(len(appTable.Processes[0].Instances)) 376 maxMemory = strings.Fields(instanceSummary.Memory)[2] 377 maxDiskSize = strings.Fields(instanceSummary.Disk)[2] 378 }) 379 380 It("the action should be a no-op", func() { 381 session = helpers.CF("v3-scale", appName, "-i", currentInstances, "-m", maxMemory, "-k", maxDiskSize) 382 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 383 Consistently(session).ShouldNot(Say("This will cause the app to restart")) 384 Consistently(session).ShouldNot(Say("Stopping")) 385 Consistently(session).ShouldNot(Say("Starting")) 386 Consistently(session).ShouldNot(Say("Waiting for app to start")) 387 Eventually(session).Should(Exit(0)) 388 389 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 390 Expect(appTable.Processes).To(HaveLen(1)) 391 392 newProcessSummary := appTable.Processes[0] 393 newInstanceSummary := newProcessSummary.Instances[0] 394 Expect(newProcessSummary.Title).To(MatchRegexp(fmt.Sprintf(`web:\d/%s`, currentInstances))) 395 Expect(newInstanceSummary.Memory).To(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, maxMemory))) 396 Expect(newInstanceSummary.Disk).To(MatchRegexp(fmt.Sprintf(`\d+(\.\d+)?[KMG]? of %s`, maxDiskSize))) 397 }) 398 }) 399 400 Context("when the process flag is provided", func() { 401 It("scales the requested process", func() { 402 session := helpers.CF("v3-scale", appName, "-i", "2", "--process", "console") 403 Eventually(session).Should(Say("Scaling app %s in org %s / space %s as %s\\.\\.\\.", appName, orgName, spaceName, userName)) 404 Eventually(session).Should(Exit(0)) 405 406 appTable := helpers.ParseV3AppProcessTable(session.Out.Contents()) 407 Expect(appTable.Processes).To(HaveLen(3)) 408 409 processSummary := appTable.Processes[1] 410 instanceSummary := processSummary.Instances[0] 411 Expect(processSummary.Instances).To(HaveLen(2)) 412 Expect(processSummary.Title).To(MatchRegexp(`console:\d/2`)) 413 Expect(instanceSummary.Memory).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 414 Expect(instanceSummary.Disk).To(MatchRegexp(`\d+(\.\d+)?[KMG]? of \d+[KMG]`)) 415 }) 416 }) 417 }) 418 }) 419 420 Context("when invalid scale option values are provided", func() { 421 Context("when a negative value is passed to a flag argument", func() { 422 It("outputs an error message to the user, provides help text, and exits 1", func() { 423 session := helpers.CF("v3-scale", "some-app", "-i=-5") 424 Eventually(session.Err).Should(Say("Incorrect Usage: invalid argument for flag '-i' \\(expected int > 0\\)")) 425 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 426 Eventually(session).Should(Exit(1)) 427 428 session = helpers.CF("v3-scale", "some-app", "-k=-5") 429 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 430 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 431 Eventually(session).Should(Exit(1)) 432 433 session = helpers.CF("v3-scale", "some-app", "-m=-5") 434 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 435 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 436 Eventually(session).Should(Exit(1)) 437 }) 438 }) 439 440 Context("when a non-integer value is passed to a flag argument", func() { 441 It("outputs an error message to the user, provides help text, and exits 1", func() { 442 session := helpers.CF("v3-scale", "some-app", "-i", "not-an-integer") 443 Eventually(session.Err).Should(Say("Incorrect Usage: invalid argument for flag '-i' \\(expected int > 0\\)")) 444 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 445 Eventually(session).Should(Exit(1)) 446 447 session = helpers.CF("v3-scale", "some-app", "-k", "not-an-integer") 448 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 449 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 450 Eventually(session).Should(Exit(1)) 451 452 session = helpers.CF("v3-scale", "some-app", "-m", "not-an-integer") 453 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 454 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 455 Eventually(session).Should(Exit(1)) 456 }) 457 }) 458 459 Context("when the unit of measurement is not provided", func() { 460 It("outputs an error message to the user, provides help text, and exits 1", func() { 461 session := helpers.CF("v3-scale", "some-app", "-k", "9") 462 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 463 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 464 Eventually(session).Should(Exit(1)) 465 466 session = helpers.CF("v3-scale", "some-app", "-m", "7") 467 Eventually(session.Err).Should(Say("Byte quantity must be an integer with a unit of measurement like M, MB, G, or GB")) 468 Eventually(session).Should(Say("cf v3-scale APP_NAME")) // help 469 Eventually(session).Should(Exit(1)) 470 }) 471 }) 472 }) 473 })