github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/commands/application/start_test.go (about) 1 package application_test 2 3 import ( 4 "os" 5 "time" 6 7 "github.com/cloudfoundry/cli/cf/api" 8 "github.com/cloudfoundry/cli/cf/api/app_instances" 9 testAppInstanaces "github.com/cloudfoundry/cli/cf/api/app_instances/fakes" 10 "github.com/cloudfoundry/cli/cf/api/applications" 11 testApplication "github.com/cloudfoundry/cli/cf/api/applications/fakes" 12 testapi "github.com/cloudfoundry/cli/cf/api/fakes" 13 . "github.com/cloudfoundry/cli/cf/commands/application" 14 "github.com/cloudfoundry/cli/cf/configuration/core_config" 15 "github.com/cloudfoundry/cli/cf/errors" 16 "github.com/cloudfoundry/cli/cf/models" 17 testcmd "github.com/cloudfoundry/cli/testhelpers/commands" 18 testconfig "github.com/cloudfoundry/cli/testhelpers/configuration" 19 testlogs "github.com/cloudfoundry/cli/testhelpers/logs" 20 testreq "github.com/cloudfoundry/cli/testhelpers/requirements" 21 testterm "github.com/cloudfoundry/cli/testhelpers/terminal" 22 "github.com/cloudfoundry/loggregatorlib/logmessage" 23 . "github.com/onsi/ginkgo" 24 . "github.com/onsi/gomega" 25 26 . "github.com/cloudfoundry/cli/testhelpers/matchers" 27 ) 28 29 var _ = Describe("start command", func() { 30 var ( 31 ui *testterm.FakeUI 32 defaultAppForStart = models.Application{} 33 defaultInstanceResponses = [][]models.AppInstanceFields{} 34 defaultInstanceErrorCodes = []string{"", ""} 35 requirementsFactory *testreq.FakeReqFactory 36 logsForTail []*logmessage.LogMessage 37 logRepo *testapi.FakeLogsRepository 38 ) 39 40 getInstance := func(appGuid string) (instances []models.AppInstanceFields, apiErr error) { 41 if len(defaultInstanceResponses) > 0 { 42 instances = defaultInstanceResponses[0] 43 if len(defaultInstanceResponses) > 1 { 44 defaultInstanceResponses = defaultInstanceResponses[1:] 45 } 46 } 47 if len(defaultInstanceErrorCodes) > 0 { 48 errorCode := defaultInstanceErrorCodes[0] 49 if len(defaultInstanceErrorCodes) > 1 { 50 defaultInstanceErrorCodes = defaultInstanceErrorCodes[1:] 51 } 52 if errorCode != "" { 53 apiErr = errors.NewHttpError(400, errorCode, "Error staging app") 54 } 55 } 56 return 57 } 58 59 BeforeEach(func() { 60 ui = new(testterm.FakeUI) 61 requirementsFactory = &testreq.FakeReqFactory{} 62 63 defaultAppForStart.Name = "my-app" 64 defaultAppForStart.Guid = "my-app-guid" 65 defaultAppForStart.InstanceCount = 2 66 67 domain := models.DomainFields{} 68 domain.Name = "example.com" 69 70 route := models.RouteSummary{} 71 route.Host = "my-app" 72 route.Domain = domain 73 74 defaultAppForStart.Routes = []models.RouteSummary{route} 75 76 instance1 := models.AppInstanceFields{} 77 instance1.State = models.InstanceStarting 78 79 instance2 := models.AppInstanceFields{} 80 instance2.State = models.InstanceStarting 81 82 instance3 := models.AppInstanceFields{} 83 instance3.State = models.InstanceRunning 84 85 instance4 := models.AppInstanceFields{} 86 instance4.State = models.InstanceStarting 87 88 defaultInstanceResponses = [][]models.AppInstanceFields{ 89 []models.AppInstanceFields{instance1, instance2}, 90 []models.AppInstanceFields{instance1, instance2}, 91 []models.AppInstanceFields{instance3, instance4}, 92 } 93 94 logsForTail = []*logmessage.LogMessage{} 95 logRepo = new(testapi.FakeLogsRepository) 96 logRepo.TailLogsForStub = func(appGuid string, onConnect func(), onMessage func(*logmessage.LogMessage)) error { 97 onConnect() 98 for _, log := range logsForTail { 99 onMessage(log) 100 } 101 return nil 102 } 103 }) 104 105 callStart := func(args []string, config core_config.Reader, requirementsFactory *testreq.FakeReqFactory, displayApp ApplicationDisplayer, appRepo applications.ApplicationRepository, appInstancesRepo app_instances.AppInstancesRepository, logRepo api.LogsRepository) (ui *testterm.FakeUI) { 106 ui = new(testterm.FakeUI) 107 108 cmd := NewStart(ui, config, displayApp, appRepo, appInstancesRepo, logRepo) 109 cmd.StagingTimeout = 100 * time.Millisecond 110 cmd.StartupTimeout = 200 * time.Millisecond 111 cmd.PingerThrottle = 50 * time.Millisecond 112 113 testcmd.RunCommand(cmd, args, requirementsFactory) 114 return 115 } 116 117 startAppWithInstancesAndErrors := func(displayApp ApplicationDisplayer, app models.Application, requirementsFactory *testreq.FakeReqFactory) (ui *testterm.FakeUI, appRepo *testApplication.FakeApplicationRepository, appInstancesRepo *testAppInstanaces.FakeAppInstancesRepository) { 118 configRepo := testconfig.NewRepositoryWithDefaults() 119 appRepo = &testApplication.FakeApplicationRepository{ 120 UpdateAppResult: app, 121 } 122 appRepo.ReadReturns.App = app 123 appInstancesRepo = &testAppInstanaces.FakeAppInstancesRepository{} 124 appInstancesRepo.GetInstancesStub = getInstance 125 126 logsForTail = []*logmessage.LogMessage{ 127 testlogs.NewLogMessage("Log Line 1", app.Guid, LogMessageTypeStaging, time.Now()), 128 testlogs.NewLogMessage("Log Line 2", app.Guid, LogMessageTypeStaging, time.Now()), 129 } 130 131 args := []string{"my-app"} 132 133 requirementsFactory.Application = app 134 ui = callStart(args, configRepo, requirementsFactory, displayApp, appRepo, appInstancesRepo, logRepo) 135 return 136 } 137 138 It("fails requirements when not logged in", func() { 139 requirementsFactory.LoginSuccess = false 140 cmd := NewStart(new(testterm.FakeUI), testconfig.NewRepository(), &testcmd.FakeAppDisplayer{}, &testApplication.FakeApplicationRepository{}, &testAppInstanaces.FakeAppInstancesRepository{}, &testapi.FakeLogsRepository{}) 141 142 Expect(testcmd.RunCommand(cmd, []string{"some-app-name"}, requirementsFactory)).To(BeFalse()) 143 }) 144 145 Describe("timeouts", func() { 146 It("has sane default timeout values", func() { 147 cmd := NewStart(new(testterm.FakeUI), testconfig.NewRepository(), &testcmd.FakeAppDisplayer{}, &testApplication.FakeApplicationRepository{}, &testAppInstanaces.FakeAppInstancesRepository{}, &testapi.FakeLogsRepository{}) 148 Expect(cmd.StagingTimeout).To(Equal(15 * time.Minute)) 149 Expect(cmd.StartupTimeout).To(Equal(5 * time.Minute)) 150 }) 151 152 It("can read timeout values from environment variables", func() { 153 oldStaging := os.Getenv("CF_STAGING_TIMEOUT") 154 oldStart := os.Getenv("CF_STARTUP_TIMEOUT") 155 defer func() { 156 os.Setenv("CF_STAGING_TIMEOUT", oldStaging) 157 os.Setenv("CF_STARTUP_TIMEOUT", oldStart) 158 }() 159 160 os.Setenv("CF_STAGING_TIMEOUT", "6") 161 os.Setenv("CF_STARTUP_TIMEOUT", "3") 162 cmd := NewStart(new(testterm.FakeUI), testconfig.NewRepository(), &testcmd.FakeAppDisplayer{}, &testApplication.FakeApplicationRepository{}, &testAppInstanaces.FakeAppInstancesRepository{}, &testapi.FakeLogsRepository{}) 163 Expect(cmd.StagingTimeout).To(Equal(6 * time.Minute)) 164 Expect(cmd.StartupTimeout).To(Equal(3 * time.Minute)) 165 }) 166 167 Describe("when the staging timeout is zero seconds", func() { 168 var ( 169 app models.Application 170 cmd *Start 171 ) 172 173 BeforeEach(func() { 174 app = defaultAppForStart 175 176 instances := []models.AppInstanceFields{models.AppInstanceFields{}} 177 appRepo := &testApplication.FakeApplicationRepository{ 178 UpdateAppResult: app, 179 } 180 appRepo.ReadReturns.App = app 181 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 182 appInstancesRepo.GetInstancesReturns(instances, errors.New("Error staging app")) 183 184 requirementsFactory.LoginSuccess = true 185 requirementsFactory.Application = app 186 config := testconfig.NewRepository() 187 displayApp := &testcmd.FakeAppDisplayer{} 188 189 cmd = NewStart(ui, config, displayApp, appRepo, appInstancesRepo, logRepo) 190 cmd.StagingTimeout = 1 191 cmd.PingerThrottle = 1 192 cmd.StartupTimeout = 1 193 }) 194 195 It("can still respond to staging failures", func() { 196 testcmd.RunCommand(cmd, []string{"my-app"}, requirementsFactory) 197 198 Expect(ui.Outputs).To(ContainSubstrings( 199 []string{"my-app"}, 200 []string{"FAILED"}, 201 []string{"Error staging app"}, 202 )) 203 }) 204 }) 205 }) 206 207 Context("when logged in", func() { 208 BeforeEach(func() { 209 requirementsFactory.LoginSuccess = true 210 }) 211 212 It("fails with usage when not provided exactly one arg", func() { 213 config := testconfig.NewRepository() 214 displayApp := &testcmd.FakeAppDisplayer{} 215 appRepo := &testApplication.FakeApplicationRepository{} 216 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 217 logRepo := &testapi.FakeLogsRepository{} 218 219 ui := callStart([]string{}, config, requirementsFactory, displayApp, appRepo, appInstancesRepo, logRepo) 220 Expect(ui.FailedWithUsage).To(BeTrue()) 221 }) 222 223 It("uses uses proper org name and space name", func() { 224 config := testconfig.NewRepositoryWithDefaults() 225 displayApp := &testcmd.FakeAppDisplayer{} 226 appRepo := &testApplication.FakeApplicationRepository{} 227 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 228 229 appRepo.ReadReturns.App = defaultAppForStart 230 appInstancesRepo = &testAppInstanaces.FakeAppInstancesRepository{} 231 appInstancesRepo.GetInstancesStub = getInstance 232 233 cmd := NewStart(ui, config, displayApp, appRepo, appInstancesRepo, logRepo) 234 cmd.StagingTimeout = 100 * time.Millisecond 235 cmd.StartupTimeout = 200 * time.Millisecond 236 cmd.PingerThrottle = 50 * time.Millisecond 237 cmd.ApplicationStart(defaultAppForStart, "some-org", "some-space") 238 239 Expect(ui.Outputs).To(ContainSubstrings( 240 []string{"my-app", "some-org", "some-space", "my-user"}, 241 []string{"OK"}, 242 )) 243 }) 244 245 It("starts an app, when given the app's name", func() { 246 displayApp := &testcmd.FakeAppDisplayer{} 247 ui, appRepo, _ := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 248 249 Expect(ui.Outputs).To(ContainSubstrings( 250 []string{"my-app", "my-org", "my-space", "my-user"}, 251 []string{"OK"}, 252 []string{"0 of 2 instances running", "2 starting"}, 253 []string{"started"}, 254 )) 255 256 Expect(requirementsFactory.ApplicationName).To(Equal("my-app")) 257 Expect(appRepo.UpdateAppGuid).To(Equal("my-app-guid")) 258 Expect(displayApp.AppToDisplay).To(Equal(defaultAppForStart)) 259 }) 260 261 It("displays the command start command instead of the detected start command when set", func() { 262 defaultAppForStart.Command = "command start command" 263 defaultAppForStart.DetectedStartCommand = "detected start command" 264 displayApp := &testcmd.FakeAppDisplayer{} 265 ui, appRepo, _ := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 266 267 Expect(appRepo.ReadCalls).To(Equal(1)) 268 Expect(ui.Outputs).To(ContainSubstrings( 269 []string{"App my-app was started using this command `command start command`"}, 270 )) 271 }) 272 273 It("displays the detected start command when no other command is set", func() { 274 defaultAppForStart.DetectedStartCommand = "detected start command" 275 defaultAppForStart.Command = "" 276 displayApp := &testcmd.FakeAppDisplayer{} 277 ui, appRepo, _ := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 278 279 Expect(appRepo.ReadCalls).To(Equal(1)) 280 Expect(ui.Outputs).To(ContainSubstrings( 281 []string{"App my-app was started using this command `detected start command`"}, 282 )) 283 }) 284 285 It("only displays staging logs when an app is starting", func() { 286 displayApp := &testcmd.FakeAppDisplayer{} 287 requirementsFactory.Application = defaultAppForStart 288 appRepo := &testApplication.FakeApplicationRepository{ 289 UpdateAppResult: defaultAppForStart, 290 } 291 appRepo.ReadReturns.App = defaultAppForStart 292 293 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 294 295 currentTime := time.Now() 296 wrongSourceName := "DEA" 297 correctSourceName := "STG" 298 299 logsForTail = []*logmessage.LogMessage{ 300 testlogs.NewLogMessage("Log Line 1", defaultAppForStart.Guid, wrongSourceName, currentTime), 301 testlogs.NewLogMessage("Log Line 2", defaultAppForStart.Guid, correctSourceName, currentTime), 302 testlogs.NewLogMessage("Log Line 3", defaultAppForStart.Guid, correctSourceName, currentTime), 303 testlogs.NewLogMessage("Log Line 4", defaultAppForStart.Guid, wrongSourceName, currentTime), 304 } 305 306 ui := callStart([]string{"my-app"}, testconfig.NewRepository(), requirementsFactory, displayApp, appRepo, appInstancesRepo, logRepo) 307 308 Expect(ui.Outputs).To(ContainSubstrings( 309 []string{"Log Line 2"}, 310 []string{"Log Line 3"}, 311 )) 312 Expect(ui.Outputs).ToNot(ContainSubstrings( 313 []string{"Log Line 1"}, 314 []string{"Log Line 4"}, 315 )) 316 }) 317 318 It("gracefully handles starting an app that is still staging", func() { 319 displayApp := &testcmd.FakeAppDisplayer{} 320 321 logRepoClosed := make(chan struct{}) 322 323 logRepo.TailLogsForStub = func(appGuid string, onConnect func(), onMessage func(*logmessage.LogMessage)) error { 324 onConnect() 325 onMessage(testlogs.NewLogMessage("Before close", appGuid, LogMessageTypeStaging, time.Now())) 326 327 <-logRepoClosed 328 329 time.Sleep(50 * time.Millisecond) 330 onMessage(testlogs.NewLogMessage("After close 1", appGuid, LogMessageTypeStaging, time.Now())) 331 onMessage(testlogs.NewLogMessage("After close 2", appGuid, LogMessageTypeStaging, time.Now())) 332 333 return nil 334 } 335 336 logRepo.CloseStub = func() { 337 close(logRepoClosed) 338 } 339 340 defaultInstanceResponses = [][]models.AppInstanceFields{ 341 []models.AppInstanceFields{}, 342 []models.AppInstanceFields{}, 343 []models.AppInstanceFields{{State: models.InstanceDown}, {State: models.InstanceStarting}}, 344 []models.AppInstanceFields{{State: models.InstanceStarting}, {State: models.InstanceStarting}}, 345 []models.AppInstanceFields{{State: models.InstanceRunning}, {State: models.InstanceRunning}}, 346 } 347 348 defaultInstanceErrorCodes = []string{errors.APP_NOT_STAGED, errors.APP_NOT_STAGED, "", "", ""} 349 350 ui, _, appInstancesRepo := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 351 352 Expect(appInstancesRepo.GetInstancesArgsForCall(0)).To(Equal("my-app-guid")) 353 354 Expect(ui.Outputs).To(ContainSubstrings( 355 []string{"Before close"}, 356 []string{"After close 1"}, 357 []string{"After close 2"}, 358 []string{"my-app failed to stage within", "minutes"}, 359 )) 360 }) 361 362 It("displays an error message when staging fails", func() { 363 displayApp := &testcmd.FakeAppDisplayer{} 364 defaultInstanceResponses = [][]models.AppInstanceFields{[]models.AppInstanceFields{}} 365 defaultInstanceErrorCodes = []string{"170001"} 366 367 ui, _, _ := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 368 369 Expect(ui.Outputs).To(ContainSubstrings( 370 []string{"my-app"}, 371 []string{"FAILED"}, 372 []string{"Error staging app"}, 373 )) 374 }) 375 376 Context("when an app instance is flapping", func() { 377 It("fails and alerts the user", func() { 378 displayApp := &testcmd.FakeAppDisplayer{} 379 appInstance := models.AppInstanceFields{} 380 appInstance.State = models.InstanceStarting 381 appInstance2 := models.AppInstanceFields{} 382 appInstance2.State = models.InstanceStarting 383 appInstance3 := models.AppInstanceFields{} 384 appInstance3.State = models.InstanceStarting 385 appInstance4 := models.AppInstanceFields{} 386 appInstance4.State = models.InstanceFlapping 387 defaultInstanceResponses = [][]models.AppInstanceFields{ 388 []models.AppInstanceFields{appInstance, appInstance2}, 389 []models.AppInstanceFields{appInstance3, appInstance4}, 390 } 391 392 defaultInstanceErrorCodes = []string{"", ""} 393 394 ui, _, _ := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 395 396 Expect(ui.Outputs).To(ContainSubstrings( 397 []string{"my-app"}, 398 []string{"0 of 2 instances running", "1 starting", "1 failing"}, 399 []string{"FAILED"}, 400 []string{"Start unsuccessful"}, 401 )) 402 }) 403 }) 404 405 It("tells the user about the failure when waiting for the app to start times out", func() { 406 displayApp := &testcmd.FakeAppDisplayer{} 407 appInstance := models.AppInstanceFields{} 408 appInstance.State = models.InstanceStarting 409 appInstance2 := models.AppInstanceFields{} 410 appInstance2.State = models.InstanceStarting 411 appInstance3 := models.AppInstanceFields{} 412 appInstance3.State = models.InstanceStarting 413 appInstance4 := models.AppInstanceFields{} 414 appInstance4.State = models.InstanceDown 415 appInstance5 := models.AppInstanceFields{} 416 appInstance5.State = models.InstanceDown 417 appInstance6 := models.AppInstanceFields{} 418 appInstance6.State = models.InstanceDown 419 defaultInstanceResponses = [][]models.AppInstanceFields{ 420 []models.AppInstanceFields{appInstance, appInstance2}, 421 []models.AppInstanceFields{appInstance3, appInstance4}, 422 []models.AppInstanceFields{appInstance5, appInstance6}, 423 } 424 425 defaultInstanceErrorCodes = []string{errors.APP_NOT_STAGED, errors.APP_NOT_STAGED, errors.APP_NOT_STAGED} 426 427 ui, _, _ := startAppWithInstancesAndErrors(displayApp, defaultAppForStart, requirementsFactory) 428 429 Expect(ui.Outputs).To(ContainSubstrings( 430 []string{"Starting", "my-app"}, 431 []string{"FAILED"}, 432 []string{"my-app failed to stage within", "minutes"}, 433 )) 434 Expect(ui.Outputs).ToNot(ContainSubstrings([]string{"instances running"})) 435 }) 436 437 It("tells the user about the failure when starting the app fails", func() { 438 config := testconfig.NewRepository() 439 displayApp := &testcmd.FakeAppDisplayer{} 440 app := models.Application{} 441 app.Name = "my-app" 442 app.Guid = "my-app-guid" 443 appRepo := &testApplication.FakeApplicationRepository{UpdateErr: true} 444 appRepo.ReadReturns.App = app 445 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 446 args := []string{"my-app"} 447 requirementsFactory.Application = app 448 ui := callStart(args, config, requirementsFactory, displayApp, appRepo, appInstancesRepo, logRepo) 449 450 Expect(ui.Outputs).To(ContainSubstrings( 451 []string{"my-app"}, 452 []string{"FAILED"}, 453 []string{"Error updating app."}, 454 )) 455 Expect(appRepo.UpdateAppGuid).To(Equal("my-app-guid")) 456 }) 457 458 It("warns the user when the app is already running", func() { 459 displayApp := &testcmd.FakeAppDisplayer{} 460 config := testconfig.NewRepository() 461 app := models.Application{} 462 app.Name = "my-app" 463 app.Guid = "my-app-guid" 464 app.State = "started" 465 appRepo := &testApplication.FakeApplicationRepository{} 466 appRepo.ReadReturns.App = app 467 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 468 469 requirementsFactory.Application = app 470 471 args := []string{"my-app"} 472 ui := callStart(args, config, requirementsFactory, displayApp, appRepo, appInstancesRepo, logRepo) 473 474 Expect(ui.Outputs).To(ContainSubstrings([]string{"my-app", "is already started"})) 475 476 Expect(appRepo.UpdateAppGuid).To(Equal("")) 477 }) 478 479 It("tells the user when connecting to the log server fails", func() { 480 configRepo := testconfig.NewRepositoryWithDefaults() 481 displayApp := &testcmd.FakeAppDisplayer{} 482 483 appRepo := &testApplication.FakeApplicationRepository{} 484 appRepo.ReadReturns.App = defaultAppForStart 485 appInstancesRepo := &testAppInstanaces.FakeAppInstancesRepository{} 486 487 logRepo.TailLogsForReturns(errors.New("Ooops")) 488 489 requirementsFactory.Application = defaultAppForStart 490 491 ui := callStart([]string{"my-app"}, configRepo, requirementsFactory, displayApp, appRepo, appInstancesRepo, logRepo) 492 493 Expect(ui.Outputs).To(ContainSubstrings( 494 []string{"error tailing logs"}, 495 []string{"Ooops"}, 496 )) 497 }) 498 }) 499 })