github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/worker/placement_test.go (about)

     1  package worker_test
     2  
     3  import (
     4  	"errors"
     5  
     6  	"code.cloudfoundry.org/lager"
     7  	"code.cloudfoundry.org/lager/lagertest"
     8  	"github.com/pf-qiu/concourse/v6/atc/db"
     9  	. "github.com/pf-qiu/concourse/v6/atc/worker"
    10  	"github.com/pf-qiu/concourse/v6/atc/worker/workerfakes"
    11  
    12  	. "github.com/onsi/ginkgo"
    13  	. "github.com/onsi/gomega"
    14  )
    15  
    16  //go:generate counterfeiter . ContainerPlacementStrategy
    17  
    18  var (
    19  	strategy ContainerPlacementStrategy
    20  
    21  	spec     ContainerSpec
    22  	metadata db.ContainerMetadata
    23  	workers  []Worker
    24  
    25  	chosenWorker Worker
    26  	chooseErr    error
    27  
    28  	newStrategyError error
    29  
    30  	compatibleWorkerOneCache1 *workerfakes.FakeWorker
    31  	compatibleWorkerOneCache2 *workerfakes.FakeWorker
    32  	compatibleWorkerTwoCaches *workerfakes.FakeWorker
    33  	compatibleWorkerNoCaches1 *workerfakes.FakeWorker
    34  	compatibleWorkerNoCaches2 *workerfakes.FakeWorker
    35  
    36  	logger *lagertest.TestLogger
    37  )
    38  
    39  var _ = Describe("FewestBuildContainersPlacementStrategy", func() {
    40  	Describe("Choose", func() {
    41  		var compatibleWorker1 *workerfakes.FakeWorker
    42  		var compatibleWorker2 *workerfakes.FakeWorker
    43  		var compatibleWorker3 *workerfakes.FakeWorker
    44  
    45  		BeforeEach(func() {
    46  			logger = lagertest.NewTestLogger("build-containers-equal-placement-test")
    47  			strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"fewest-build-containers"}})
    48  			Expect(newStrategyError).ToNot(HaveOccurred())
    49  			compatibleWorker1 = new(workerfakes.FakeWorker)
    50  			compatibleWorker1.NameReturns("compatibleWorker1")
    51  			compatibleWorker2 = new(workerfakes.FakeWorker)
    52  			compatibleWorker2.NameReturns("compatibleWorker2")
    53  			compatibleWorker3 = new(workerfakes.FakeWorker)
    54  			compatibleWorker3.NameReturns("compatibleWorker3")
    55  
    56  			spec = ContainerSpec{
    57  				ImageSpec: ImageSpec{ResourceType: "some-type"},
    58  
    59  				TeamID: 4567,
    60  
    61  				Inputs: []InputSource{},
    62  			}
    63  		})
    64  
    65  		Context("when there is only one worker", func() {
    66  			BeforeEach(func() {
    67  				workers = []Worker{compatibleWorker1}
    68  				compatibleWorker1.BuildContainersReturns(20)
    69  			})
    70  
    71  			It("picks that worker", func() {
    72  				chosenWorker, chooseErr = strategy.Choose(
    73  					logger,
    74  					workers,
    75  					spec,
    76  				)
    77  				Expect(chooseErr).ToNot(HaveOccurred())
    78  				Expect(chosenWorker).To(Equal(compatibleWorker1))
    79  			})
    80  		})
    81  
    82  		Context("when there are multiple workers", func() {
    83  			BeforeEach(func() {
    84  				workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
    85  
    86  				compatibleWorker1.BuildContainersReturns(30)
    87  				compatibleWorker2.BuildContainersReturns(20)
    88  				compatibleWorker3.BuildContainersReturns(10)
    89  			})
    90  
    91  			Context("when the container is not of type 'check'", func() {
    92  				It("picks the one with least amount of containers", func() {
    93  					Consistently(func() Worker {
    94  						chosenWorker, chooseErr = strategy.Choose(
    95  							logger,
    96  							workers,
    97  							spec,
    98  						)
    99  						Expect(chooseErr).ToNot(HaveOccurred())
   100  						return chosenWorker
   101  					}).Should(Equal(compatibleWorker3))
   102  				})
   103  
   104  				Context("when there is more than one worker with the same number of build containers", func() {
   105  					BeforeEach(func() {
   106  						workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
   107  						compatibleWorker1.BuildContainersReturns(10)
   108  					})
   109  
   110  					It("picks any of them", func() {
   111  						Consistently(func() Worker {
   112  							chosenWorker, chooseErr = strategy.Choose(
   113  								logger,
   114  								workers,
   115  								spec,
   116  							)
   117  							Expect(chooseErr).ToNot(HaveOccurred())
   118  							return chosenWorker
   119  						}).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker3)))
   120  					})
   121  				})
   122  
   123  			})
   124  		})
   125  	})
   126  })
   127  
   128  var _ = Describe("VolumeLocalityPlacementStrategy", func() {
   129  	Describe("Choose", func() {
   130  		JustBeforeEach(func() {
   131  			chosenWorker, chooseErr = strategy.Choose(
   132  				logger,
   133  				workers,
   134  				spec,
   135  			)
   136  		})
   137  
   138  		BeforeEach(func() {
   139  			logger = lagertest.NewTestLogger("volume-locality-placement-test")
   140  			strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"volume-locality"}})
   141  			Expect(newStrategyError).ToNot(HaveOccurred())
   142  
   143  			fakeInput1 := new(workerfakes.FakeInputSource)
   144  			fakeInput1AS := new(workerfakes.FakeArtifactSource)
   145  			fakeInput1AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) {
   146  				switch worker {
   147  				case compatibleWorkerOneCache1, compatibleWorkerOneCache2, compatibleWorkerTwoCaches:
   148  					return new(workerfakes.FakeVolume), true, nil
   149  				default:
   150  					return nil, false, nil
   151  				}
   152  			}
   153  			fakeInput1.SourceReturns(fakeInput1AS)
   154  
   155  			fakeInput2 := new(workerfakes.FakeInputSource)
   156  			fakeInput2AS := new(workerfakes.FakeArtifactSource)
   157  			fakeInput2AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) {
   158  				switch worker {
   159  				case compatibleWorkerTwoCaches:
   160  					return new(workerfakes.FakeVolume), true, nil
   161  				default:
   162  					return nil, false, nil
   163  				}
   164  			}
   165  			fakeInput2.SourceReturns(fakeInput2AS)
   166  
   167  			spec = ContainerSpec{
   168  				ImageSpec: ImageSpec{ResourceType: "some-type"},
   169  
   170  				TeamID: 4567,
   171  
   172  				Inputs: []InputSource{
   173  					fakeInput1,
   174  					fakeInput2,
   175  				},
   176  			}
   177  
   178  			compatibleWorkerOneCache1 = new(workerfakes.FakeWorker)
   179  			compatibleWorkerOneCache1.SatisfiesReturns(true)
   180  			compatibleWorkerOneCache1.NameReturns("compatibleWorkerOneCache1")
   181  
   182  			compatibleWorkerOneCache2 = new(workerfakes.FakeWorker)
   183  			compatibleWorkerOneCache2.SatisfiesReturns(true)
   184  			compatibleWorkerOneCache2.NameReturns("compatibleWorkerOneCache2")
   185  
   186  			compatibleWorkerTwoCaches = new(workerfakes.FakeWorker)
   187  			compatibleWorkerTwoCaches.SatisfiesReturns(true)
   188  			compatibleWorkerTwoCaches.NameReturns("compatibleWorkerTwoCaches")
   189  
   190  			compatibleWorkerNoCaches1 = new(workerfakes.FakeWorker)
   191  			compatibleWorkerNoCaches1.SatisfiesReturns(true)
   192  			compatibleWorkerNoCaches1.NameReturns("compatibleWorkerNoCaches1")
   193  
   194  			compatibleWorkerNoCaches2 = new(workerfakes.FakeWorker)
   195  			compatibleWorkerNoCaches2.SatisfiesReturns(true)
   196  			compatibleWorkerNoCaches2.NameReturns("compatibleWorkerNoCaches2")
   197  		})
   198  
   199  		Context("with one having the most local caches", func() {
   200  			BeforeEach(func() {
   201  				workers = []Worker{
   202  					compatibleWorkerOneCache1,
   203  					compatibleWorkerTwoCaches,
   204  					compatibleWorkerNoCaches1,
   205  					compatibleWorkerNoCaches2,
   206  				}
   207  			})
   208  
   209  			It("creates it on the worker with the most caches", func() {
   210  				Expect(chooseErr).ToNot(HaveOccurred())
   211  				Expect(chosenWorker).To(Equal(compatibleWorkerTwoCaches))
   212  			})
   213  		})
   214  
   215  		Context("with multiple with the same amount of local caches", func() {
   216  			BeforeEach(func() {
   217  				workers = []Worker{
   218  					compatibleWorkerOneCache1,
   219  					compatibleWorkerOneCache2,
   220  					compatibleWorkerNoCaches1,
   221  					compatibleWorkerNoCaches2,
   222  				}
   223  			})
   224  
   225  			It("creates it on a random one of the two", func() {
   226  				Expect(chooseErr).ToNot(HaveOccurred())
   227  				Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerOneCache1), Equal(compatibleWorkerOneCache2)))
   228  
   229  				workerChoiceCounts := map[Worker]int{}
   230  
   231  				for i := 0; i < 100; i++ {
   232  					worker, err := strategy.Choose(
   233  						logger,
   234  						workers,
   235  						spec,
   236  					)
   237  					Expect(err).ToNot(HaveOccurred())
   238  					Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerOneCache1), Equal(compatibleWorkerOneCache2)))
   239  					workerChoiceCounts[worker]++
   240  				}
   241  
   242  				Expect(workerChoiceCounts[compatibleWorkerOneCache1]).ToNot(BeZero())
   243  				Expect(workerChoiceCounts[compatibleWorkerOneCache2]).ToNot(BeZero())
   244  				Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).To(BeZero())
   245  				Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).To(BeZero())
   246  			})
   247  		})
   248  
   249  		Context("with none having any local caches", func() {
   250  			BeforeEach(func() {
   251  				workers = []Worker{
   252  					compatibleWorkerNoCaches1,
   253  					compatibleWorkerNoCaches2,
   254  				}
   255  			})
   256  
   257  			It("creates it on a random one of them", func() {
   258  				Expect(chooseErr).ToNot(HaveOccurred())
   259  				Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
   260  
   261  				workerChoiceCounts := map[Worker]int{}
   262  
   263  				for i := 0; i < 100; i++ {
   264  					worker, err := strategy.Choose(
   265  						logger,
   266  						workers,
   267  						spec,
   268  					)
   269  					Expect(err).ToNot(HaveOccurred())
   270  					Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
   271  					workerChoiceCounts[worker]++
   272  				}
   273  
   274  				Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).ToNot(BeZero())
   275  				Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).ToNot(BeZero())
   276  			})
   277  		})
   278  	})
   279  })
   280  
   281  var _ = Describe("No strategy should equal to random strategy", func() {
   282  	Describe("Choose", func() {
   283  		JustBeforeEach(func() {
   284  			chosenWorker, chooseErr = strategy.Choose(
   285  				logger,
   286  				workers,
   287  				spec,
   288  			)
   289  		})
   290  
   291  		BeforeEach(func() {
   292  			strategy = NewRandomPlacementStrategy()
   293  
   294  			workers = []Worker{
   295  				compatibleWorkerNoCaches1,
   296  				compatibleWorkerNoCaches2,
   297  			}
   298  		})
   299  
   300  		It("creates it on a random one of them", func() {
   301  			Expect(chooseErr).ToNot(HaveOccurred())
   302  			Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
   303  
   304  			workerChoiceCounts := map[Worker]int{}
   305  
   306  			for i := 0; i < 100; i++ {
   307  				worker, err := strategy.Choose(
   308  					logger,
   309  					workers,
   310  					spec,
   311  				)
   312  				Expect(err).ToNot(HaveOccurred())
   313  				Expect(chosenWorker).To(SatisfyAny(Equal(compatibleWorkerNoCaches1), Equal(compatibleWorkerNoCaches2)))
   314  				workerChoiceCounts[worker]++
   315  			}
   316  
   317  			Expect(workerChoiceCounts[compatibleWorkerNoCaches1]).ToNot(BeZero())
   318  			Expect(workerChoiceCounts[compatibleWorkerNoCaches2]).ToNot(BeZero())
   319  		})
   320  	})
   321  })
   322  
   323  var _ = Describe("LimitActiveTasksPlacementStrategy", func() {
   324  	Describe("Choose", func() {
   325  		var compatibleWorker1 *workerfakes.FakeWorker
   326  		var compatibleWorker2 *workerfakes.FakeWorker
   327  		var compatibleWorker3 *workerfakes.FakeWorker
   328  
   329  		Context("when MaxActiveTasksPerWorker less than 0", func() {
   330  			BeforeEach(func() {
   331  				logger = lagertest.NewTestLogger("active-tasks-equal-placement-test")
   332  				strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"limit-active-tasks"}, MaxActiveTasksPerWorker: -1})
   333  			})
   334  			It("should fail", func() {
   335  				Expect(newStrategyError).To(HaveOccurred())
   336  				Expect(newStrategyError).To(Equal(errors.New("max-active-tasks-per-worker must be greater or equal than 0")))
   337  				Expect(strategy).To(BeNil())
   338  			})
   339  		})
   340  
   341  		Context("when MaxActiveTasksPerWorker less than 0", func() {
   342  			BeforeEach(func() {
   343  				logger = lagertest.NewTestLogger("active-tasks-equal-placement-test")
   344  				strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"limit-active-tasks"}, MaxActiveTasksPerWorker: 0})
   345  				Expect(newStrategyError).ToNot(HaveOccurred())
   346  
   347  				compatibleWorker1 = new(workerfakes.FakeWorker)
   348  				compatibleWorker1.NameReturns("compatibleWorker1")
   349  				compatibleWorker2 = new(workerfakes.FakeWorker)
   350  				compatibleWorker2.NameReturns("compatibleWorker2")
   351  				compatibleWorker3 = new(workerfakes.FakeWorker)
   352  				compatibleWorker3.NameReturns("compatibleWorker3")
   353  
   354  				spec = ContainerSpec{
   355  					ImageSpec: ImageSpec{ResourceType: "some-type"},
   356  
   357  					Type: "task",
   358  
   359  					TeamID: 4567,
   360  
   361  					Inputs: []InputSource{},
   362  				}
   363  			})
   364  
   365  			Context("when there is only one worker with any amount of running tasks", func() {
   366  				BeforeEach(func() {
   367  					workers = []Worker{compatibleWorker1}
   368  					compatibleWorker1.ActiveTasksReturns(42, nil)
   369  				})
   370  
   371  				It("picks that worker", func() {
   372  					chosenWorker, chooseErr = strategy.Choose(
   373  						logger,
   374  						workers,
   375  						spec,
   376  					)
   377  					Expect(chooseErr).ToNot(HaveOccurred())
   378  					Expect(chosenWorker).To(Equal(compatibleWorker1))
   379  				})
   380  			})
   381  
   382  			Context("when there are multiple workers", func() {
   383  				BeforeEach(func() {
   384  					workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
   385  
   386  					compatibleWorker1.ActiveTasksReturns(2, nil)
   387  					compatibleWorker2.ActiveTasksReturns(1, nil)
   388  					compatibleWorker3.ActiveTasksReturns(2, nil)
   389  				})
   390  
   391  				It("a task picks the one with least amount of active tasks", func() {
   392  					Consistently(func() Worker {
   393  						chosenWorker, chooseErr = strategy.Choose(
   394  							logger,
   395  							workers,
   396  							spec,
   397  						)
   398  						Expect(chooseErr).ToNot(HaveOccurred())
   399  						return chosenWorker
   400  					}).Should(Equal(compatibleWorker2))
   401  				})
   402  
   403  				Context("when all the workers have the same number of active tasks", func() {
   404  					BeforeEach(func() {
   405  						workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
   406  						compatibleWorker1.ActiveTasksReturns(1, nil)
   407  						compatibleWorker2.ActiveTasksReturns(1, nil)
   408  						compatibleWorker3.ActiveTasksReturns(1, nil)
   409  					})
   410  
   411  					It("a task picks any of them", func() {
   412  						Consistently(func() Worker {
   413  							chosenWorker, chooseErr = strategy.Choose(
   414  								logger,
   415  								workers,
   416  								spec,
   417  							)
   418  							Expect(chooseErr).ToNot(HaveOccurred())
   419  							return chosenWorker
   420  						}).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker2), Equal(compatibleWorker3)))
   421  					})
   422  				})
   423  			})
   424  			Context("when max-tasks-per-worker is set to 1", func() {
   425  				BeforeEach(func() {
   426  					strategy, newStrategyError = NewContainerPlacementStrategy(ContainerPlacementStrategyOptions{ContainerPlacementStrategy: []string{"limit-active-tasks"}, MaxActiveTasksPerWorker: 1})
   427  					Expect(newStrategyError).ToNot(HaveOccurred())
   428  				})
   429  				Context("when there are multiple workers", func() {
   430  					BeforeEach(func() {
   431  						workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
   432  
   433  						compatibleWorker1.ActiveTasksReturns(1, nil)
   434  						compatibleWorker2.ActiveTasksReturns(0, nil)
   435  						compatibleWorker3.ActiveTasksReturns(1, nil)
   436  					})
   437  
   438  					It("picks the worker with no active tasks", func() {
   439  						chosenWorker, chooseErr = strategy.Choose(
   440  							logger,
   441  							workers,
   442  							spec,
   443  						)
   444  						Expect(chooseErr).ToNot(HaveOccurred())
   445  						Expect(chosenWorker).To(Equal(compatibleWorker2))
   446  					})
   447  				})
   448  
   449  				Context("when all workers have active tasks", func() {
   450  					BeforeEach(func() {
   451  						workers = []Worker{compatibleWorker1, compatibleWorker2, compatibleWorker3}
   452  
   453  						compatibleWorker1.ActiveTasksReturns(1, nil)
   454  						compatibleWorker2.ActiveTasksReturns(1, nil)
   455  						compatibleWorker3.ActiveTasksReturns(1, nil)
   456  					})
   457  
   458  					It("picks no worker", func() {
   459  						chosenWorker, chooseErr = strategy.Choose(
   460  							logger,
   461  							workers,
   462  							spec,
   463  						)
   464  						Expect(chooseErr).ToNot(HaveOccurred())
   465  						Expect(chosenWorker).To(BeNil())
   466  					})
   467  					Context("when the container is not of type 'task'", func() {
   468  						BeforeEach(func() {
   469  							spec.Type = ""
   470  						})
   471  						It("picks any worker", func() {
   472  							Consistently(func() Worker {
   473  								chosenWorker, chooseErr = strategy.Choose(
   474  									logger,
   475  									workers,
   476  									spec,
   477  								)
   478  								Expect(chooseErr).ToNot(HaveOccurred())
   479  								return chosenWorker
   480  							}).Should(Or(Equal(compatibleWorker1), Equal(compatibleWorker2), Equal(compatibleWorker3)))
   481  						})
   482  					})
   483  				})
   484  			})
   485  		})
   486  	})
   487  })
   488  
   489  var _ = Describe("ChainedPlacementStrategy #Choose", func() {
   490  
   491  	var someWorker1 *workerfakes.FakeWorker
   492  	var someWorker2 *workerfakes.FakeWorker
   493  	var someWorker3 *workerfakes.FakeWorker
   494  
   495  	BeforeEach(func() {
   496  		logger = lagertest.NewTestLogger("build-containers-equal-placement-test")
   497  		strategy, newStrategyError = NewContainerPlacementStrategy(
   498  			ContainerPlacementStrategyOptions{
   499  				ContainerPlacementStrategy: []string{"fewest-build-containers", "volume-locality"},
   500  			})
   501  		Expect(newStrategyError).ToNot(HaveOccurred())
   502  		someWorker1 = new(workerfakes.FakeWorker)
   503  		someWorker1.NameReturns("worker1")
   504  		someWorker2 = new(workerfakes.FakeWorker)
   505  		someWorker2.NameReturns("worker2")
   506  		someWorker3 = new(workerfakes.FakeWorker)
   507  		someWorker3.NameReturns("worker3")
   508  
   509  		spec = ContainerSpec{
   510  			ImageSpec: ImageSpec{ResourceType: "some-type"},
   511  
   512  			TeamID: 4567,
   513  
   514  			Inputs: []InputSource{},
   515  		}
   516  	})
   517  
   518  	Context("when there are multiple workers", func() {
   519  		BeforeEach(func() {
   520  			workers = []Worker{someWorker1, someWorker2, someWorker3}
   521  
   522  			someWorker1.BuildContainersReturns(30)
   523  			someWorker2.BuildContainersReturns(20)
   524  			someWorker3.BuildContainersReturns(10)
   525  		})
   526  
   527  		It("picks the one with least amount of containers", func() {
   528  			Consistently(func() Worker {
   529  				chosenWorker, chooseErr = strategy.Choose(
   530  					logger,
   531  					workers,
   532  					spec,
   533  				)
   534  				Expect(chooseErr).ToNot(HaveOccurred())
   535  				return chosenWorker
   536  			}).Should(Equal(someWorker3))
   537  		})
   538  
   539  		Context("when there is more than one worker with the same number of build containers", func() {
   540  			BeforeEach(func() {
   541  				workers = []Worker{someWorker1, someWorker2, someWorker3}
   542  				someWorker1.BuildContainersReturns(10)
   543  				someWorker2.BuildContainersReturns(20)
   544  				someWorker3.BuildContainersReturns(10)
   545  
   546  				fakeInput1 := new(workerfakes.FakeInputSource)
   547  				fakeInput1AS := new(workerfakes.FakeArtifactSource)
   548  				fakeInput1AS.ExistsOnStub = func(logger lager.Logger, worker Worker) (Volume, bool, error) {
   549  					switch worker {
   550  					case someWorker3:
   551  						return new(workerfakes.FakeVolume), true, nil
   552  					default:
   553  						return nil, false, nil
   554  					}
   555  				}
   556  				fakeInput1.SourceReturns(fakeInput1AS)
   557  
   558  				spec = ContainerSpec{
   559  					ImageSpec: ImageSpec{ResourceType: "some-type"},
   560  
   561  					TeamID: 4567,
   562  
   563  					Inputs: []InputSource{
   564  						fakeInput1,
   565  					},
   566  				}
   567  			})
   568  			It("picks the one with the most volumes", func() {
   569  				Consistently(func() Worker {
   570  					cWorker, cErr := strategy.Choose(
   571  						logger,
   572  						workers,
   573  						spec,
   574  					)
   575  					Expect(cErr).ToNot(HaveOccurred())
   576  					return cWorker
   577  				}).Should(Equal(someWorker3))
   578  
   579  			})
   580  		})
   581  
   582  	})
   583  })