github.com/loggregator/cli@v6.33.1-0.20180224010324-82334f081791+incompatible/actor/pushaction/apply_test.go (about) 1 package pushaction_test 2 3 import ( 4 "errors" 5 "io/ioutil" 6 7 "code.cloudfoundry.org/cli/actor/actionerror" 8 . "code.cloudfoundry.org/cli/actor/pushaction" 9 "code.cloudfoundry.org/cli/actor/pushaction/pushactionfakes" 10 "code.cloudfoundry.org/cli/actor/v2action" 11 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 12 13 . "github.com/onsi/ginkgo" 14 . "github.com/onsi/gomega" 15 ) 16 17 func streamsDrainedAndClosed(configStream <-chan ApplicationConfig, eventStream <-chan Event, warningsStream <-chan Warnings, errorStream <-chan error) bool { 18 var configStreamClosed, eventStreamClosed, warningsStreamClosed, errorStreamClosed bool 19 for { 20 select { 21 case _, ok := <-configStream: 22 if !ok { 23 configStreamClosed = true 24 } 25 case _, ok := <-eventStream: 26 if !ok { 27 eventStreamClosed = true 28 } 29 case _, ok := <-warningsStream: 30 if !ok { 31 warningsStreamClosed = true 32 } 33 case _, ok := <-errorStream: 34 if !ok { 35 errorStreamClosed = true 36 } 37 } 38 if configStreamClosed && eventStreamClosed && warningsStreamClosed && errorStreamClosed { 39 break 40 } 41 } 42 return true 43 } 44 45 var _ = Describe("Apply", func() { 46 var ( 47 actor *Actor 48 fakeV2Actor *pushactionfakes.FakeV2Actor 49 fakeSharedActor *pushactionfakes.FakeSharedActor 50 51 config ApplicationConfig 52 fakeProgressBar *pushactionfakes.FakeProgressBar 53 54 eventStream <-chan Event 55 warningsStream <-chan Warnings 56 errorStream <-chan error 57 configStream <-chan ApplicationConfig 58 ) 59 60 BeforeEach(func() { 61 fakeV2Actor = new(pushactionfakes.FakeV2Actor) 62 fakeSharedActor = new(pushactionfakes.FakeSharedActor) 63 actor = NewActor(fakeV2Actor, fakeSharedActor) 64 config = ApplicationConfig{ 65 DesiredApplication: Application{ 66 Application: v2action.Application{ 67 Name: "some-app-name", 68 SpaceGUID: "some-space-guid", 69 }}, 70 DesiredRoutes: []v2action.Route{{Host: "banana"}}, 71 Path: "some-path", 72 } 73 fakeProgressBar = new(pushactionfakes.FakeProgressBar) 74 }) 75 76 JustBeforeEach(func() { 77 configStream, eventStream, warningsStream, errorStream = actor.Apply(config, fakeProgressBar) 78 }) 79 80 AfterEach(func() { 81 Eventually(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).Should(BeTrue()) 82 }) 83 84 Context("when creating/updating the application is successful", func() { 85 var createdApp v2action.Application 86 87 BeforeEach(func() { 88 fakeV2Actor.CreateApplicationStub = func(application v2action.Application) (v2action.Application, v2action.Warnings, error) { 89 createdApp = application 90 createdApp.GUID = "some-app-guid" 91 92 return createdApp, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, nil 93 } 94 }) 95 96 JustBeforeEach(func() { 97 Eventually(eventStream).Should(Receive(Equal(SettingUpApplication))) 98 Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2"))) 99 Eventually(eventStream).Should(Receive(Equal(CreatedApplication))) 100 }) 101 102 Context("when the route creation is successful", func() { 103 var createdRoutes []v2action.Route 104 105 BeforeEach(func() { 106 createdRoutes = []v2action.Route{{Host: "banana", GUID: "some-route-guid"}} 107 fakeV2Actor.CreateRouteReturns(createdRoutes[0], v2action.Warnings{"create-route-warnings-1", "create-route-warnings-2"}, nil) 108 }) 109 110 JustBeforeEach(func() { 111 Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes))) 112 Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warnings-1", "create-route-warnings-2"))) 113 Eventually(eventStream).Should(Receive(Equal(CreatedRoutes))) 114 }) 115 116 Context("when mapping the routes is successful", func() { 117 var desiredServices map[string]v2action.ServiceInstance 118 119 BeforeEach(func() { 120 desiredServices = map[string]v2action.ServiceInstance{ 121 "service_1": {Name: "service_1", GUID: "service_guid"}, 122 } 123 config.DesiredServices = desiredServices 124 fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"map-route-warnings-1", "map-route-warnings-2"}, nil) 125 }) 126 127 JustBeforeEach(func() { 128 Eventually(warningsStream).Should(Receive(ConsistOf("map-route-warnings-1", "map-route-warnings-2"))) 129 Eventually(eventStream).Should(Receive(Equal(BoundRoutes))) 130 }) 131 132 Context("when service binding is successful", func() { 133 BeforeEach(func() { 134 fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warnings-1", "bind-service-warnings-2"}, nil) 135 }) 136 137 JustBeforeEach(func() { 138 Eventually(eventStream).Should(Receive(Equal(ConfiguringServices))) 139 Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warnings-1", "bind-service-warnings-2"))) 140 Eventually(eventStream).Should(Receive(Equal(BoundServices))) 141 }) 142 143 Context("when resource matching happens", func() { 144 JustBeforeEach(func() { 145 Eventually(eventStream).Should(Receive(Equal(ResourceMatching))) 146 Eventually(warningsStream).Should(Receive(ConsistOf("resource-warnings-1", "resource-warnings-2"))) 147 }) 148 149 Context("when there is at least one resource that has not been matched", func() { 150 BeforeEach(func() { 151 fakeV2Actor.ResourceMatchReturns(nil, []v2action.Resource{{}}, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil) 152 }) 153 154 Context("when the archive creation is successful", func() { 155 var archivePath string 156 157 BeforeEach(func() { 158 tmpfile, err := ioutil.TempFile("", "fake-archive") 159 Expect(err).ToNot(HaveOccurred()) 160 _, err = tmpfile.Write([]byte("123456")) 161 Expect(err).ToNot(HaveOccurred()) 162 Expect(tmpfile.Close()).ToNot(HaveOccurred()) 163 164 archivePath = tmpfile.Name() 165 fakeSharedActor.ZipDirectoryResourcesReturns(archivePath, nil) 166 }) 167 168 JustBeforeEach(func() { 169 Eventually(eventStream).Should(Receive(Equal(CreatingArchive))) 170 }) 171 172 Context("when the upload is successful", func() { 173 BeforeEach(func() { 174 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 175 }) 176 177 JustBeforeEach(func() { 178 Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive))) 179 Eventually(eventStream).Should(Receive(Equal(UploadWithArchiveComplete))) 180 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 181 }) 182 183 It("sends the updated config and a complete event", func() { 184 Eventually(configStream).Should(Receive(Equal(ApplicationConfig{ 185 CurrentApplication: Application{Application: createdApp}, 186 CurrentRoutes: createdRoutes, 187 CurrentServices: desiredServices, 188 DesiredApplication: Application{Application: createdApp}, 189 DesiredRoutes: createdRoutes, 190 DesiredServices: desiredServices, 191 UnmatchedResources: []v2action.Resource{{}}, 192 Path: "some-path", 193 }))) 194 Eventually(eventStream).Should(Receive(Equal(Complete))) 195 196 Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1)) 197 }) 198 }) 199 200 Context("when the upload errors", func() { 201 Context("with a retryable error", func() { 202 BeforeEach(func() { 203 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, ccerror.PipeSeekError{}) 204 }) 205 206 It("retries the download up to three times", func() { 207 Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive))) 208 Eventually(fakeProgressBar.NewProgressBarWrapperCallCount).Should(Equal(1)) 209 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 210 Eventually(eventStream).Should(Receive(Equal(RetryUpload))) 211 212 Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive))) 213 Eventually(fakeProgressBar.NewProgressBarWrapperCallCount).Should(Equal(2)) 214 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 215 Eventually(eventStream).Should(Receive(Equal(RetryUpload))) 216 217 Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive))) 218 Eventually(fakeProgressBar.NewProgressBarWrapperCallCount).Should(Equal(3)) 219 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 220 Eventually(eventStream).Should(Receive(Equal(RetryUpload))) 221 222 Eventually(errorStream).Should(Receive(Equal(actionerror.UploadFailedError{}))) 223 }) 224 }) 225 226 Context("with a generic error", func() { 227 var expectedErr error 228 229 BeforeEach(func() { 230 expectedErr = errors.New("dios mio") 231 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, expectedErr) 232 }) 233 234 It("sends warnings and errors, then stops", func() { 235 Eventually(eventStream).Should(Receive(Equal(UploadingApplicationWithArchive))) 236 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 237 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 238 Consistently(eventStream).ShouldNot(Receive()) 239 }) 240 }) 241 }) 242 }) 243 244 Context("when the archive creation errors", func() { 245 var expectedErr error 246 247 BeforeEach(func() { 248 expectedErr = errors.New("dios mio") 249 fakeSharedActor.ZipDirectoryResourcesReturns("", expectedErr) 250 }) 251 252 It("sends warnings and errors, then stops", func() { 253 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 254 Consistently(eventStream).ShouldNot(Receive()) 255 }) 256 }) 257 }) 258 259 Context("when all the resources have been matched", func() { 260 BeforeEach(func() { 261 fakeV2Actor.ResourceMatchReturns(nil, nil, v2action.Warnings{"resource-warnings-1", "resource-warnings-2"}, nil) 262 }) 263 264 JustBeforeEach(func() { 265 Eventually(eventStream).Should(Receive(Equal(UploadingApplication))) 266 Eventually(warningsStream).Should(Receive(ConsistOf("upload-warnings-1", "upload-warnings-2"))) 267 }) 268 269 Context("when the upload is successful", func() { 270 BeforeEach(func() { 271 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, nil) 272 }) 273 274 It("sends the updated config and a complete event", func() { 275 Eventually(configStream).Should(Receive(Equal(ApplicationConfig{ 276 CurrentApplication: Application{Application: createdApp}, 277 CurrentRoutes: createdRoutes, 278 CurrentServices: desiredServices, 279 DesiredApplication: Application{Application: createdApp}, 280 DesiredRoutes: createdRoutes, 281 DesiredServices: desiredServices, 282 Path: "some-path", 283 }))) 284 Eventually(eventStream).Should(Receive(Equal(Complete))) 285 286 Expect(fakeV2Actor.UploadApplicationPackageCallCount()).To(Equal(1)) 287 _, _, reader, readerLength := fakeV2Actor.UploadApplicationPackageArgsForCall(0) 288 Expect(reader).To(BeNil()) 289 Expect(readerLength).To(BeNumerically("==", 0)) 290 }) 291 }) 292 293 Context("when the upload errors", func() { 294 var expectedErr error 295 296 BeforeEach(func() { 297 expectedErr = errors.New("dios mio") 298 fakeV2Actor.UploadApplicationPackageReturns(v2action.Job{}, v2action.Warnings{"upload-warnings-1", "upload-warnings-2"}, expectedErr) 299 }) 300 301 It("sends warnings and errors, then stops", func() { 302 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 303 Consistently(eventStream).ShouldNot(Receive()) 304 }) 305 }) 306 }) 307 }) 308 309 Context("when a docker image is provided", func() { 310 BeforeEach(func() { 311 config.DesiredApplication.DockerImage = "some-docker-image-path" 312 }) 313 314 It("skips achiving and uploading", func() { 315 Eventually(configStream).Should(Receive()) 316 Eventually(eventStream).Should(Receive(Equal(Complete))) 317 318 Expect(fakeSharedActor.ZipDirectoryResourcesCallCount()).To(Equal(0)) 319 }) 320 }) 321 }) 322 323 Context("when there are no services to bind", func() { 324 BeforeEach(func() { 325 services := map[string]v2action.ServiceInstance{ 326 "service_1": {Name: "service_1", GUID: "service_guid"}, 327 } 328 config.CurrentServices = services 329 config.DesiredServices = services 330 }) 331 332 It("should not send the BoundServices event", func() { 333 Eventually(eventStream).ShouldNot(Receive(Equal(ConfiguringServices))) 334 Consistently(eventStream).ShouldNot(Receive(Equal(BoundServices))) 335 }) 336 }) 337 338 Context("when mapping routes fails", func() { 339 var expectedErr error 340 341 BeforeEach(func() { 342 expectedErr = errors.New("dios mio") 343 fakeV2Actor.BindServiceByApplicationAndServiceInstanceReturns(v2action.Warnings{"bind-service-warnings-1", "bind-service-warnings-2"}, expectedErr) 344 }) 345 346 It("sends warnings and errors, then stops", func() { 347 Eventually(eventStream).Should(Receive(Equal(ConfiguringServices))) 348 Eventually(warningsStream).Should(Receive(ConsistOf("bind-service-warnings-1", "bind-service-warnings-2"))) 349 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 350 Consistently(eventStream).ShouldNot(Receive()) 351 }) 352 }) 353 }) 354 355 Context("when there are no routes to map", func() { 356 BeforeEach(func() { 357 config.CurrentRoutes = createdRoutes 358 }) 359 360 It("should not send the RouteCreated event", func() { 361 Eventually(warningsStream).Should(Receive()) 362 Consistently(eventStream).ShouldNot(Receive(Equal(CreatedRoutes))) 363 }) 364 }) 365 366 Context("when mapping the routes errors", func() { 367 var expectedErr error 368 369 BeforeEach(func() { 370 expectedErr = errors.New("dios mio") 371 fakeV2Actor.MapRouteToApplicationReturns(v2action.Warnings{"map-route-warnings-1", "map-route-warnings-2"}, expectedErr) 372 }) 373 374 It("sends warnings and errors, then stops", func() { 375 Eventually(warningsStream).Should(Receive(ConsistOf("map-route-warnings-1", "map-route-warnings-2"))) 376 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 377 Consistently(eventStream).ShouldNot(Receive()) 378 }) 379 }) 380 }) 381 382 Context("when there are no routes to create", func() { 383 BeforeEach(func() { 384 config.DesiredRoutes[0].GUID = "some-route-guid" 385 }) 386 387 It("should not send the RouteCreated event", func() { 388 Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes))) 389 Eventually(warningsStream).Should(Receive()) 390 Consistently(eventStream).ShouldNot(Receive()) 391 }) 392 }) 393 394 Context("when the route creation errors", func() { 395 var expectedErr error 396 397 BeforeEach(func() { 398 expectedErr = errors.New("dios mio") 399 fakeV2Actor.CreateRouteReturns(v2action.Route{}, v2action.Warnings{"create-route-warnings-1", "create-route-warnings-2"}, expectedErr) 400 }) 401 402 It("sends warnings and errors, then stops", func() { 403 Eventually(eventStream).Should(Receive(Equal(CreatingAndMappingRoutes))) 404 Eventually(warningsStream).Should(Receive(ConsistOf("create-route-warnings-1", "create-route-warnings-2"))) 405 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 406 Consistently(eventStream).ShouldNot(Receive()) 407 }) 408 }) 409 410 Context("when routes are to be removed", func() { 411 BeforeEach(func() { 412 config.NoRoute = true 413 }) 414 415 Context("when there are routes", func() { 416 BeforeEach(func() { 417 config.CurrentRoutes = []v2action.Route{{GUID: "some-route-guid-1"}, {GUID: "some-route-guid-2"}} 418 }) 419 420 JustBeforeEach(func() { 421 Eventually(eventStream).Should(Receive(Equal(UnmappingRoutes))) 422 }) 423 424 Context("when the unmap is successful", func() { 425 BeforeEach(func() { 426 fakeV2Actor.UnmapRouteFromApplicationReturnsOnCall(0, v2action.Warnings{"unmapping-route-warnings-1"}, nil) 427 fakeV2Actor.UnmapRouteFromApplicationReturnsOnCall(1, v2action.Warnings{"unmapping-route-warnings-2"}, nil) 428 }) 429 430 It("unmaps the routes and returns all warnings", func() { 431 Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings-1", "unmapping-route-warnings-2"))) 432 Expect(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).To(BeTrue()) 433 434 Expect(fakeV2Actor.UnmapRouteFromApplicationCallCount()).To(Equal(2)) 435 }) 436 }) 437 438 Context("when unmapping routes fails", func() { 439 var expectedErr error 440 441 BeforeEach(func() { 442 expectedErr = errors.New("dios mio") 443 fakeV2Actor.UnmapRouteFromApplicationReturns(v2action.Warnings{"unmapping-route-warnings-1", "unmapping-route-warnings-2"}, expectedErr) 444 }) 445 446 It("sends warnings and errors, then stops", func() { 447 Eventually(warningsStream).Should(Receive(ConsistOf("unmapping-route-warnings-1", "unmapping-route-warnings-2"))) 448 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 449 Consistently(eventStream).ShouldNot(Receive()) 450 }) 451 }) 452 }) 453 454 Context("when there are no routes", func() { 455 BeforeEach(func() { 456 config.CurrentRoutes = nil 457 }) 458 459 It("does not send an UnmappingRoutes event", func() { 460 Consistently(eventStream).ShouldNot(Receive(Equal(UnmappingRoutes))) 461 Expect(streamsDrainedAndClosed(configStream, eventStream, warningsStream, errorStream)).To(BeTrue()) 462 463 Expect(fakeV2Actor.UnmapRouteFromApplicationCallCount()).To(Equal(0)) 464 }) 465 }) 466 }) 467 }) 468 469 Context("when creating/updating errors", func() { 470 var expectedErr error 471 472 BeforeEach(func() { 473 expectedErr = errors.New("dios mio") 474 fakeV2Actor.CreateApplicationReturns(v2action.Application{}, v2action.Warnings{"create-application-warnings-1", "create-application-warnings-2"}, expectedErr) 475 }) 476 477 It("sends warnings and errors, then stops", func() { 478 Eventually(eventStream).Should(Receive(Equal(SettingUpApplication))) 479 Eventually(warningsStream).Should(Receive(ConsistOf("create-application-warnings-1", "create-application-warnings-2"))) 480 Eventually(errorStream).Should(Receive(MatchError(expectedErr))) 481 Consistently(eventStream).ShouldNot(Receive()) 482 }) 483 }) 484 })