github.com/cloudfoundry/cli@v7.1.0+incompatible/actor/v3action/zdt_test.go (about)

     1  package v3action_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"time"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    10  	"code.cloudfoundry.org/cli/resources"
    11  
    12  	. "code.cloudfoundry.org/cli/actor/v3action"
    13  	"code.cloudfoundry.org/cli/actor/v3action/v3actionfakes"
    14  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3"
    15  	. "github.com/onsi/ginkgo"
    16  	. "github.com/onsi/gomega"
    17  )
    18  
    19  var _ = Describe("v3-zdt-push", func() {
    20  
    21  	var (
    22  		actor                     *Actor
    23  		fakeCloudControllerClient *v3actionfakes.FakeCloudControllerClient
    24  		fakeConfig                *v3actionfakes.FakeConfig
    25  	)
    26  
    27  	BeforeEach(func() {
    28  		fakeCloudControllerClient = new(v3actionfakes.FakeCloudControllerClient)
    29  		fakeConfig = new(v3actionfakes.FakeConfig)
    30  		actor = NewActor(fakeCloudControllerClient, fakeConfig, nil, nil)
    31  	})
    32  
    33  	Describe("CancelDeploymentByAppNameAndSpace", func() {
    34  		var (
    35  			app resources.Application
    36  		)
    37  
    38  		BeforeEach(func() {
    39  			app = resources.Application{GUID: "app-guid"}
    40  			fakeCloudControllerClient.GetApplicationsReturns([]resources.Application{app}, ccv3.Warnings{"getapp-warning"}, nil)
    41  			fakeCloudControllerClient.GetDeploymentsReturns([]ccv3.Deployment{{GUID: "deployment-guid"}}, ccv3.Warnings{"getdep-warning"}, nil)
    42  			fakeCloudControllerClient.CancelDeploymentReturns(ccv3.Warnings{"cancel-warning"}, nil)
    43  		})
    44  
    45  		It("cancels the appropriate deployment", func() {
    46  			warnings, err := actor.CancelDeploymentByAppNameAndSpace("app-name", "space-guid")
    47  			Expect(err).NotTo(HaveOccurred())
    48  			Expect(warnings).To(ConsistOf(Warnings{"getapp-warning", "getdep-warning", "cancel-warning"}))
    49  			Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).To(ConsistOf(
    50  				ccv3.Query{Key: ccv3.NameFilter, Values: []string{"app-name"}},
    51  				ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{"space-guid"}},
    52  			))
    53  			Expect(fakeCloudControllerClient.GetDeploymentsArgsForCall(0)).To(ConsistOf(
    54  				ccv3.Query{Key: ccv3.AppGUIDFilter, Values: []string{"app-guid"}},
    55  				ccv3.Query{Key: ccv3.PerPage, Values: []string{"1"}},
    56  				ccv3.Query{Key: ccv3.OrderBy, Values: []string{ccv3.CreatedAtDescendingOrder}},
    57  			))
    58  			Expect(fakeCloudControllerClient.CancelDeploymentArgsForCall(0)).To(Equal("deployment-guid"))
    59  		})
    60  
    61  		Context("when no deployments are found", func() {
    62  			BeforeEach(func() {
    63  				fakeCloudControllerClient.GetDeploymentsReturns([]ccv3.Deployment{}, nil, nil)
    64  			})
    65  
    66  			It("errors appropriately", func() {
    67  				_, err := actor.CancelDeploymentByAppNameAndSpace("app-name", "space-guid")
    68  				Expect(err).To(MatchError("failed to find a deployment for that app"))
    69  			})
    70  		})
    71  
    72  		Context("when we fail while searching for app", func() {
    73  			BeforeEach(func() {
    74  				fakeCloudControllerClient.GetApplicationsReturns(nil, nil, errors.New("banana"))
    75  			})
    76  
    77  			It("errors appropriately", func() {
    78  				_, err := actor.CancelDeploymentByAppNameAndSpace("app-name", "space-guid")
    79  				Expect(err).To(MatchError("banana"))
    80  			})
    81  		})
    82  
    83  		Context("when we fail while searching for the apps current deployment", func() {
    84  			BeforeEach(func() {
    85  				fakeCloudControllerClient.GetDeploymentsReturns(nil, nil, errors.New("vegetable"))
    86  			})
    87  
    88  			It("errors appropriately", func() {
    89  				_, err := actor.CancelDeploymentByAppNameAndSpace("app-name", "space-guid")
    90  				Expect(err).To(MatchError("vegetable"))
    91  			})
    92  		})
    93  	})
    94  
    95  	Describe("CreateApplicationDeployment", func() {
    96  
    97  		Context("When there is no error", func() {
    98  
    99  			BeforeEach(func() {
   100  				fakeCloudControllerClient.CreateApplicationDeploymentReturns("some-deployment-guid", ccv3.Warnings{"create-deployment-warning"}, nil)
   101  			})
   102  
   103  			It("Returns the deployment GUID when it is non empty", func() {
   104  				deploymentGUID, warnings, err := actor.CreateDeployment("some-app-guid", "some-droplet-guid")
   105  				Expect(deploymentGUID).To(Equal("some-deployment-guid"))
   106  				Expect(warnings).To(ConsistOf("create-deployment-warning"))
   107  				Expect(err).To(BeNil())
   108  			})
   109  		})
   110  
   111  		Context("When an error occurs", func() {
   112  
   113  			BeforeEach(func() {
   114  				fakeCloudControllerClient.CreateApplicationDeploymentReturns("", ccv3.Warnings{"create-deployment-warning"}, errors.New("failed create"))
   115  			})
   116  
   117  			It("Returns an error if an error occurred", func() {
   118  				deploymentGUID, warnings, err := actor.CreateDeployment("some-app-guid", "some-droplet-guid")
   119  				Expect(deploymentGUID).To(Equal(""))
   120  				Expect(warnings).To(ConsistOf("create-deployment-warning"))
   121  				Expect(err).To(MatchError(errors.New("failed create")))
   122  			})
   123  		})
   124  
   125  	})
   126  
   127  	Describe("GetDeploymentState", func() {
   128  
   129  		Context("when there is no error", func() {
   130  
   131  			BeforeEach(func() {
   132  				resultDeployment := ccv3.Deployment{State: constant.DeploymentDeploying}
   133  				fakeCloudControllerClient.GetDeploymentReturns(resultDeployment, ccv3.Warnings{"create-deployment-warning"}, nil)
   134  			})
   135  
   136  			It("returns a state of X", func() {
   137  				deploymentState, warnings, err := actor.GetDeploymentState("some-deployment-guid")
   138  				Expect(deploymentState).To(Equal(constant.DeploymentDeploying))
   139  				Expect(warnings).To(ConsistOf("create-deployment-warning"))
   140  				Expect(err).To(BeNil())
   141  			})
   142  		})
   143  	})
   144  
   145  	Describe("PollDeployment", func() {
   146  		var warningsChannel chan Warnings
   147  		var allWarnings Warnings
   148  		var funcDone chan interface{}
   149  
   150  		BeforeEach(func() {
   151  			fakeConfig.StartupTimeoutReturns(time.Second)
   152  			fakeConfig.PollingIntervalReturns(0)
   153  			warningsChannel = make(chan Warnings)
   154  			allWarnings = Warnings{}
   155  			funcDone = make(chan interface{})
   156  			go func() {
   157  				for {
   158  					select {
   159  					case warnings := <-warningsChannel:
   160  						allWarnings = append(allWarnings, warnings...)
   161  					case <-funcDone:
   162  						return
   163  					}
   164  				}
   165  			}()
   166  		})
   167  
   168  		const myDeploymentGUID = "another-great-deployment-guid"
   169  
   170  		Context("When the deployment eventually deploys", func() {
   171  			BeforeEach(func() {
   172  				getDeploymentCallCount := 0
   173  
   174  				fakeCloudControllerClient.GetDeploymentStub = func(deploymentGuid string) (ccv3.Deployment, ccv3.Warnings, error) {
   175  					defer func() { getDeploymentCallCount++ }()
   176  					if getDeploymentCallCount == 0 {
   177  						return ccv3.Deployment{State: constant.DeploymentDeploying},
   178  							ccv3.Warnings{"get-process-warning-1", "get-process-warning-2"},
   179  							nil
   180  					} else {
   181  						return ccv3.Deployment{State: constant.DeploymentDeployed},
   182  							ccv3.Warnings{fmt.Sprintf("get-process-warning-%d", getDeploymentCallCount+2)},
   183  							nil
   184  					}
   185  				}
   186  			})
   187  
   188  			It("returns a nil error", func() {
   189  				err := actor.PollDeployment(myDeploymentGUID, warningsChannel)
   190  				funcDone <- nil
   191  				Expect(err).To(BeNil())
   192  				Expect(allWarnings).To(ConsistOf("get-process-warning-1", "get-process-warning-2", "get-process-warning-3"))
   193  			})
   194  		})
   195  		Context("When the deployment is cancelled", func() {
   196  			BeforeEach(func() {
   197  				getDeploymentCallCount := 0
   198  
   199  				fakeCloudControllerClient.GetDeploymentStub = func(deploymentGuid string) (ccv3.Deployment, ccv3.Warnings, error) {
   200  					defer func() { getDeploymentCallCount++ }()
   201  					if getDeploymentCallCount == 0 {
   202  						return ccv3.Deployment{State: constant.DeploymentDeploying},
   203  							ccv3.Warnings{"get-process-warning-1", "get-process-warning-2"},
   204  							nil
   205  					} else {
   206  						return ccv3.Deployment{State: constant.DeploymentCanceled},
   207  							ccv3.Warnings{fmt.Sprintf("get-process-warning-%d", getDeploymentCallCount+2)},
   208  							nil
   209  					}
   210  				}
   211  			})
   212  			It("throws a deployment canceled error", func() {
   213  				err := actor.PollDeployment(myDeploymentGUID, warningsChannel)
   214  				funcDone <- nil
   215  				Expect(err).To(MatchError(errors.New("Deployment has been canceled")))
   216  				Expect(allWarnings).To(ConsistOf("get-process-warning-1", "get-process-warning-2", "get-process-warning-3"))
   217  			})
   218  
   219  		})
   220  
   221  		Context("When waiting for the deployment to finish times out", func() {
   222  			BeforeEach(func() {
   223  				fakeConfig.StartupTimeoutReturns(time.Millisecond)
   224  				fakeConfig.PollingIntervalReturns(time.Millisecond * 2)
   225  				fakeCloudControllerClient.GetDeploymentReturns(ccv3.Deployment{State: constant.DeploymentDeploying}, ccv3.Warnings{"some-deployment-warning"}, nil)
   226  			})
   227  
   228  			It("Throws a timeout error", func() {
   229  				err := actor.PollDeployment(myDeploymentGUID, warningsChannel)
   230  				funcDone <- nil
   231  				Expect(err).To(MatchError(actionerror.StartupTimeoutError{}))
   232  				Expect(allWarnings).To(ConsistOf("some-deployment-warning"))
   233  			})
   234  		})
   235  	})
   236  
   237  	Describe("ZeroDowntimePollStart", func() {
   238  		var warningsChannel chan Warnings
   239  		var allWarnings Warnings
   240  		var funcDone chan interface{}
   241  
   242  		BeforeEach(func() {
   243  			warningsChannel = make(chan Warnings)
   244  			funcDone = make(chan interface{})
   245  			allWarnings = Warnings{}
   246  			go func() {
   247  				for {
   248  					select {
   249  					case warnings := <-warningsChannel:
   250  						allWarnings = append(allWarnings, warnings...)
   251  					case <-funcDone:
   252  						return
   253  					}
   254  				}
   255  			}()
   256  		})
   257  
   258  		Context("when getting the application processes fails", func() {
   259  			BeforeEach(func() {
   260  				fakeCloudControllerClient.GetApplicationProcessesReturns(nil, ccv3.Warnings{"get-app-warning-1", "get-app-warning-2"}, errors.New("some-error"))
   261  			})
   262  
   263  			It("returns the error and all warnings", func() {
   264  				err := actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   265  				funcDone <- nil
   266  				Expect(allWarnings).To(ConsistOf("get-app-warning-1", "get-app-warning-2"))
   267  				Expect(err).To(MatchError(errors.New("some-error")))
   268  			})
   269  		})
   270  
   271  		Context("when getting the application processes succeeds", func() {
   272  			var processes []ccv3.Process
   273  
   274  			BeforeEach(func() {
   275  				fakeConfig.StartupTimeoutReturns(time.Second)
   276  				fakeConfig.PollingIntervalReturns(0)
   277  				processes = []ccv3.Process{
   278  					{GUID: "web-guid", Type: "web"},
   279  					{GUID: "web-ish-guid", Type: "web-deployment-efg456"},
   280  				}
   281  			})
   282  
   283  			JustBeforeEach(func() {
   284  				fakeCloudControllerClient.GetApplicationProcessesReturns(
   285  					processes,
   286  					ccv3.Warnings{"get-app-warning-1"}, nil)
   287  			})
   288  
   289  			Context("when the polling times out", func() {
   290  				BeforeEach(func() {
   291  					fakeConfig.StartupTimeoutReturns(time.Millisecond)
   292  					fakeConfig.PollingIntervalReturns(time.Millisecond * 2)
   293  					fakeCloudControllerClient.GetProcessInstancesReturns(
   294  						[]ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}},
   295  						ccv3.Warnings{"get-process-warning-1", "get-process-warning-2"},
   296  						nil,
   297  					)
   298  				})
   299  
   300  				It("returns the timeout error", func() {
   301  					err := actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   302  					funcDone <- nil
   303  
   304  					Expect(allWarnings).To(ConsistOf("get-app-warning-1", "get-process-warning-1", "get-process-warning-2"))
   305  					Expect(err).To(MatchError(actionerror.StartupTimeoutError{}))
   306  				})
   307  
   308  				It("gets polling and timeout values from the config", func() {
   309  					err := actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   310  					funcDone <- nil
   311  
   312  					Expect(fakeConfig.StartupTimeoutCallCount()).To(Equal(1))
   313  					Expect(fakeConfig.PollingIntervalCallCount()).To(Equal(1))
   314  					Expect(err).To(MatchError(actionerror.StartupTimeoutError{}))
   315  				})
   316  			})
   317  
   318  			Context("when getting the process instances errors", func() {
   319  				BeforeEach(func() {
   320  					fakeCloudControllerClient.GetProcessInstancesReturns(
   321  						nil,
   322  						ccv3.Warnings{"get-process-warning-1", "get-process-warning-2"},
   323  						errors.New("some-error"),
   324  					)
   325  				})
   326  
   327  				It("returns the error", func() {
   328  					err := actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   329  					funcDone <- nil
   330  
   331  					Expect(allWarnings).To(ConsistOf("get-app-warning-1", "get-process-warning-1", "get-process-warning-2"))
   332  					Expect(err).To(MatchError("some-error"))
   333  				})
   334  			})
   335  
   336  			Context("when getting the process instances succeeds", func() {
   337  				var (
   338  					processInstanceCallCount  int
   339  					processInstancesCallGuids []string
   340  					initialInstanceStates     []ccv3.ProcessInstance
   341  					eventualInstanceStates    []ccv3.ProcessInstance
   342  					pollStartErr              error
   343  				)
   344  
   345  				BeforeEach(func() {
   346  					processInstanceCallCount = 0
   347  					processInstancesCallGuids = []string{}
   348  
   349  					fakeCloudControllerClient.GetProcessInstancesStub = func(processGuid string) ([]ccv3.ProcessInstance, ccv3.Warnings, error) {
   350  						processInstancesCallGuids = append(processInstancesCallGuids, processGuid)
   351  						defer func() { processInstanceCallCount++ }()
   352  						if processInstanceCallCount == 0 {
   353  							return initialInstanceStates,
   354  								ccv3.Warnings{"get-process-warning-1", "get-process-warning-2"},
   355  								nil
   356  						} else {
   357  							return eventualInstanceStates,
   358  								ccv3.Warnings{fmt.Sprintf("get-process-warning-%d", processInstanceCallCount+2)},
   359  								nil
   360  						}
   361  					}
   362  				})
   363  
   364  				Context("when there are no instances for the deploying process", func() {
   365  					BeforeEach(func() {
   366  						initialInstanceStates = []ccv3.ProcessInstance{}
   367  					})
   368  
   369  					It("should not return an error", func() {
   370  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   371  						funcDone <- nil
   372  
   373  						Expect(pollStartErr).NotTo(HaveOccurred())
   374  					})
   375  
   376  					It("should only call GetProcessInstances once before exiting", func() {
   377  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   378  						funcDone <- nil
   379  
   380  						Expect(processInstanceCallCount).To(Equal(1))
   381  					})
   382  
   383  					It("should return correct warnings", func() {
   384  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   385  						funcDone <- nil
   386  
   387  						Expect(allWarnings).To(ConsistOf("get-app-warning-1", "get-process-warning-1", "get-process-warning-2"))
   388  					})
   389  				})
   390  
   391  				Context("when the deploying process has at least one running instance by the second call", func() {
   392  					BeforeEach(func() {
   393  						initialInstanceStates = []ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}, {State: constant.ProcessInstanceStarting}, {State: constant.ProcessInstanceStarting}}
   394  						eventualInstanceStates = []ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}, {State: constant.ProcessInstanceStarting}, {State: constant.ProcessInstanceRunning}}
   395  					})
   396  
   397  					It("should not return an error", func() {
   398  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   399  						funcDone <- nil
   400  
   401  						Expect(pollStartErr).NotTo(HaveOccurred())
   402  					})
   403  
   404  					It("should call GetProcessInstances twice", func() {
   405  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   406  						funcDone <- nil
   407  
   408  						Expect(processInstanceCallCount).To(Equal(2))
   409  					})
   410  
   411  					It("should return correct warnings", func() {
   412  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   413  						funcDone <- nil
   414  
   415  						Expect(allWarnings).To(ConsistOf("get-app-warning-1", "get-process-warning-1", "get-process-warning-2", "get-process-warning-3"))
   416  					})
   417  
   418  					It("should only call GetProcessInstances for the webish process", func() {
   419  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   420  						funcDone <- nil
   421  
   422  						Expect(processInstancesCallGuids).To(ConsistOf("web-ish-guid", "web-ish-guid"))
   423  					})
   424  				})
   425  
   426  				Context("when there is no webish process", func() {
   427  					BeforeEach(func() {
   428  						fakeConfig.StartupTimeoutReturns(time.Second)
   429  						fakeConfig.PollingIntervalReturns(0)
   430  						processes = []ccv3.Process{
   431  							{GUID: "web-guid", Type: "web"},
   432  							{GUID: "worker-guid", Type: "worker"},
   433  						}
   434  					})
   435  
   436  					It("should not return an error", func() {
   437  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   438  						funcDone <- nil
   439  
   440  						Expect(pollStartErr).NotTo(HaveOccurred())
   441  					})
   442  
   443  					It("should call not call GetProcessInstances, because the deploy has already succeeded", func() {
   444  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   445  						funcDone <- nil
   446  
   447  						Expect(processInstanceCallCount).To(Equal(0))
   448  					})
   449  
   450  					It("should return correct warnings", func() {
   451  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   452  						funcDone <- nil
   453  
   454  						Expect(allWarnings).To(ConsistOf("get-app-warning-1"))
   455  					})
   456  				})
   457  
   458  				Context("when all of the instances have crashed by the second call", func() {
   459  					BeforeEach(func() {
   460  						initialInstanceStates = []ccv3.ProcessInstance{{State: constant.ProcessInstanceStarting}, {State: constant.ProcessInstanceStarting}, {State: constant.ProcessInstanceStarting}}
   461  						eventualInstanceStates = []ccv3.ProcessInstance{{State: constant.ProcessInstanceCrashed}, {State: constant.ProcessInstanceCrashed}, {State: constant.ProcessInstanceCrashed}}
   462  					})
   463  
   464  					It("should not return an error", func() {
   465  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   466  						funcDone <- nil
   467  
   468  						Expect(pollStartErr).NotTo(HaveOccurred())
   469  					})
   470  
   471  					It("should call GetProcessInstances twice", func() {
   472  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   473  						funcDone <- nil
   474  
   475  						Expect(processInstanceCallCount).To(Equal(2))
   476  					})
   477  
   478  					It("should return correct warnings", func() {
   479  						pollStartErr = actor.ZeroDowntimePollStart("some-guid", warningsChannel)
   480  						funcDone <- nil
   481  
   482  						Expect(allWarnings).To(ConsistOf("get-app-warning-1", "get-process-warning-1", "get-process-warning-2", "get-process-warning-3"))
   483  					})
   484  				})
   485  			})
   486  
   487  		})
   488  	})
   489  })