github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/caasoperator/caasoperator_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasoperator_test
     5  
     6  import (
     7  	"net/url"
     8  	"os"
     9  	"path/filepath"
    10  	"time"
    11  
    12  	"github.com/juju/clock"
    13  	"github.com/juju/clock/testclock"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/names/v5"
    17  	"github.com/juju/retry"
    18  	"github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/juju/utils/v3"
    21  	"github.com/juju/utils/v3/symlink"
    22  	"github.com/juju/worker/v3"
    23  	"github.com/juju/worker/v3/workertest"
    24  	gc "gopkg.in/check.v1"
    25  
    26  	agenttools "github.com/juju/juju/agent/tools"
    27  	apiuniter "github.com/juju/juju/api/agent/uniter"
    28  	"github.com/juju/juju/caas"
    29  	"github.com/juju/juju/caas/kubernetes/provider/exec"
    30  	"github.com/juju/juju/core/leadership"
    31  	"github.com/juju/juju/core/life"
    32  	"github.com/juju/juju/core/status"
    33  	"github.com/juju/juju/core/watcher/watchertest"
    34  	"github.com/juju/juju/downloader"
    35  	"github.com/juju/juju/juju/sockets"
    36  	"github.com/juju/juju/testcharms"
    37  	coretesting "github.com/juju/juju/testing"
    38  	"github.com/juju/juju/worker/caasoperator"
    39  	"github.com/juju/juju/worker/uniter"
    40  	"github.com/juju/juju/worker/uniter/remotestate"
    41  	runnertesting "github.com/juju/juju/worker/uniter/runner/testing"
    42  )
    43  
    44  type WorkerSuite struct {
    45  	testing.IsolationSuite
    46  
    47  	clock                 *testclock.Clock
    48  	config                caasoperator.Config
    49  	unitsChanges          chan []string
    50  	containerChanges      chan []string
    51  	appChanges            chan struct{}
    52  	appWatched            chan struct{}
    53  	unitRemoved           chan struct{}
    54  	client                fakeClient
    55  	charmDownloader       fakeDownloader
    56  	charmSHA256           string
    57  	uniterParams          *uniter.UniterParams
    58  	leadershipTrackerFunc func(unitTag names.UnitTag) leadership.TrackerWorker
    59  	uniterFacadeFunc      func(unitTag names.UnitTag) *apiuniter.State
    60  	resourceFacadeFunc    func(unitTag names.UnitTag) (*apiuniter.ResourcesFacadeClient, error)
    61  	payloadFacadeFunc     func() *apiuniter.PayloadFacadeClient
    62  	runListenerSocketFunc func(*uniter.SocketConfig) (*sockets.Socket, error)
    63  	mockExecutor          *mockExecutor
    64  }
    65  
    66  var _ = gc.Suite(&WorkerSuite{})
    67  
    68  func sockPath(c *gc.C) sockets.Socket {
    69  	sockPath := filepath.Join(c.MkDir(), "test.listener")
    70  	return sockets.Socket{Address: sockPath, Network: "unix"}
    71  }
    72  
    73  func (s *WorkerSuite) SetUpTest(c *gc.C) {
    74  	s.IsolationSuite.SetUpTest(c)
    75  
    76  	// Create a charm archive, and compute its SHA256 hash
    77  	// for comparison in the tests.
    78  	fakeDownloadDir := c.MkDir()
    79  	s.charmDownloader = fakeDownloader{
    80  		path: testcharms.Repo.CharmArchivePath(
    81  			fakeDownloadDir,
    82  			"../kubernetes/gitlab",
    83  		),
    84  	}
    85  	charmSHA256, _, err := utils.ReadFileSHA256(s.charmDownloader.path)
    86  	c.Assert(err, jc.ErrorIsNil)
    87  	s.charmSHA256 = charmSHA256
    88  
    89  	s.clock = testclock.NewClock(time.Time{})
    90  	s.appWatched = make(chan struct{}, 1)
    91  	s.unitRemoved = make(chan struct{}, 1)
    92  	s.client = fakeClient{
    93  		applicationWatched: s.appWatched,
    94  		unitRemoved:        s.unitRemoved,
    95  		life:               life.Alive,
    96  	}
    97  	s.unitsChanges = make(chan []string)
    98  	s.containerChanges = make(chan []string)
    99  	s.appChanges = make(chan struct{})
   100  	s.client.unitsWatcher = watchertest.NewMockStringsWatcher(s.unitsChanges)
   101  	s.client.containerWatcher = watchertest.NewMockStringsWatcher(s.containerChanges)
   102  	s.client.watcher = watchertest.NewMockNotifyWatcher(s.appChanges)
   103  	s.charmDownloader.ResetCalls()
   104  	s.uniterParams = &uniter.UniterParams{
   105  		Logger: loggo.GetLogger("uniter"),
   106  	}
   107  	s.leadershipTrackerFunc = func(unitTag names.UnitTag) leadership.TrackerWorker {
   108  		return &runnertesting.FakeTracker{}
   109  	}
   110  	s.uniterFacadeFunc = func(unitTag names.UnitTag) *apiuniter.State {
   111  		return &apiuniter.State{}
   112  	}
   113  	s.resourceFacadeFunc = func(unitTag names.UnitTag) (*apiuniter.ResourcesFacadeClient, error) {
   114  		return &apiuniter.ResourcesFacadeClient{}, nil
   115  	}
   116  	s.payloadFacadeFunc = func() *apiuniter.PayloadFacadeClient {
   117  		return &apiuniter.PayloadFacadeClient{}
   118  	}
   119  	s.runListenerSocketFunc = func(*uniter.SocketConfig) (*sockets.Socket, error) {
   120  		socket := sockPath(c)
   121  		return &socket, nil
   122  	}
   123  	s.mockExecutor = &mockExecutor{}
   124  
   125  	s.config = caasoperator.Config{
   126  		Application:           "gitlab",
   127  		CharmGetter:           &s.client,
   128  		Clock:                 s.clock,
   129  		DataDir:               c.MkDir(),
   130  		ProfileDir:            c.MkDir(),
   131  		Downloader:            &s.charmDownloader,
   132  		StatusSetter:          &s.client,
   133  		ApplicationWatcher:    &s.client,
   134  		ContainerStartWatcher: &s.client,
   135  		UnitGetter:            &s.client,
   136  		UnitRemover:           &s.client,
   137  		VersionSetter:         &s.client,
   138  		UniterParams:          s.uniterParams,
   139  		LeadershipTrackerFunc: s.leadershipTrackerFunc,
   140  		UniterFacadeFunc:      s.uniterFacadeFunc,
   141  		ResourcesFacadeFunc:   s.resourceFacadeFunc,
   142  		PayloadFacadeFunc:     s.payloadFacadeFunc,
   143  		RunListenerSocketFunc: s.runListenerSocketFunc,
   144  		StartUniterFunc:       func(runner *worker.Runner, params *uniter.UniterParams) error { return nil },
   145  		ExecClientGetter:      func() (exec.Executor, error) { return s.mockExecutor, nil },
   146  		Logger:                loggo.GetLogger("operator"),
   147  	}
   148  
   149  	agentBinaryDir := agenttools.ToolsDir(s.config.DataDir, "application-gitlab")
   150  	err = os.MkdirAll(agentBinaryDir, 0755)
   151  	c.Assert(err, jc.ErrorIsNil)
   152  	err = os.WriteFile(filepath.Join(s.config.DataDir, "tools", "jujud"), []byte("jujud"), 0755)
   153  	c.Assert(err, jc.ErrorIsNil)
   154  }
   155  
   156  func (s *WorkerSuite) TestValidateConfig(c *gc.C) {
   157  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   158  		config.Application = ""
   159  	}, `application name "" not valid`)
   160  
   161  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   162  		config.ProfileDir = ""
   163  	}, `missing ProfileDir not valid`)
   164  
   165  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   166  		config.ApplicationWatcher = nil
   167  	}, `missing ApplicationWatcher not valid`)
   168  
   169  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   170  		config.UnitGetter = nil
   171  	}, `missing UnitGetter not valid`)
   172  
   173  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   174  		config.UnitRemover = nil
   175  	}, `missing UnitRemover not valid`)
   176  
   177  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   178  		config.LeadershipTrackerFunc = nil
   179  	}, `missing LeadershipTrackerFunc not valid`)
   180  
   181  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   182  		config.UniterFacadeFunc = nil
   183  	}, `missing UniterFacadeFunc not valid`)
   184  
   185  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   186  		config.ResourcesFacadeFunc = nil
   187  	}, `missing ResourcesFacadeFunc not valid`)
   188  
   189  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   190  		config.PayloadFacadeFunc = nil
   191  	}, `missing PayloadFacadeFunc not valid`)
   192  
   193  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   194  		config.UniterParams = nil
   195  	}, `missing UniterParams not valid`)
   196  
   197  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   198  		config.CharmGetter = nil
   199  	}, `missing CharmGetter not valid`)
   200  
   201  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   202  		config.Clock = nil
   203  	}, `missing Clock not valid`)
   204  
   205  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   206  		config.DataDir = ""
   207  	}, `missing DataDir not valid`)
   208  
   209  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   210  		config.Downloader = nil
   211  	}, `missing Downloader not valid`)
   212  
   213  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   214  		config.StatusSetter = nil
   215  	}, `missing StatusSetter not valid`)
   216  
   217  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   218  		config.VersionSetter = nil
   219  	}, `missing VersionSetter not valid`)
   220  
   221  	s.testValidateConfig(c, func(config *caasoperator.Config) {
   222  		config.Logger = nil
   223  	}, `missing Logger not valid`)
   224  
   225  }
   226  
   227  func (s *WorkerSuite) testValidateConfig(c *gc.C, f func(*caasoperator.Config), expect string) {
   228  	config := s.config
   229  	f(&config)
   230  	w, err := caasoperator.NewWorker(config)
   231  	if err == nil {
   232  		workertest.DirtyKill(c, w)
   233  	}
   234  	c.Check(err, gc.ErrorMatches, expect)
   235  }
   236  
   237  func (s *WorkerSuite) TestStartStop(c *gc.C) {
   238  	w, err := caasoperator.NewWorker(s.config)
   239  	c.Assert(err, jc.ErrorIsNil)
   240  	workertest.CheckAlive(c, w)
   241  
   242  	retryCallArgs := retry.CallArgs{
   243  		Clock:       clock.WallClock,
   244  		MaxDuration: 500 * time.Millisecond,
   245  		Delay:       100 * time.Millisecond,
   246  		Func: func() error {
   247  			_, err = os.Stat(filepath.Join(s.config.ProfileDir, "juju-introspection.sh"))
   248  			return err
   249  		},
   250  	}
   251  	err = retry.Call(retryCallArgs)
   252  	if err != nil {
   253  		c.Fatal("missing introspection script")
   254  	}
   255  	workertest.CleanKill(c, w)
   256  }
   257  
   258  func (s *WorkerSuite) TestWorkerDownloadsCharm(c *gc.C) {
   259  	uniterStarted := make(chan struct{})
   260  	s.config.StartUniterFunc = func(runner *worker.Runner, params *uniter.UniterParams) error {
   261  		c.Assert(params.UnitTag.Id(), gc.Equals, "gitlab/0")
   262  		close(uniterStarted)
   263  		return nil
   264  	}
   265  
   266  	w, err := caasoperator.NewWorker(s.config)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	defer workertest.CleanKill(c, w)
   269  
   270  	select {
   271  	case s.appChanges <- struct{}{}:
   272  	case <-time.After(coretesting.LongWait):
   273  		c.Fatal("timed out sending application change")
   274  	}
   275  	select {
   276  	case s.unitsChanges <- []string{"gitlab/0"}:
   277  	case <-time.After(coretesting.LongWait):
   278  		c.Fatal("timed out sending unit change")
   279  	}
   280  	select {
   281  	case <-s.appWatched:
   282  	case <-time.After(coretesting.LongWait):
   283  		c.Fatal("timed out waiting for application to be watched")
   284  	}
   285  	select {
   286  	case <-uniterStarted:
   287  	case <-time.After(coretesting.LongWait):
   288  		c.Fatalf("timeout while waiting for uniter to start")
   289  	}
   290  
   291  	s.client.CheckCallNames(c, "Charm", "SetStatus", "SetVersion", "WatchUnits", "WatchContainerStart", "SetStatus", "Watch", "Charm", "Life")
   292  	s.client.CheckCall(c, 0, "Charm", "gitlab")
   293  	s.client.CheckCall(c, 2, "SetVersion", "gitlab", coretesting.CurrentVersion())
   294  	s.client.CheckCall(c, 3, "WatchUnits", "gitlab")
   295  	s.client.CheckCall(c, 4, "WatchContainerStart", "gitlab", "(?:juju-pod-init|)")
   296  	s.client.CheckCall(c, 6, "Watch", "gitlab")
   297  
   298  	s.charmDownloader.CheckCallNames(c, "Download")
   299  	downloadArgs := s.charmDownloader.Calls()[0].Args
   300  	c.Assert(downloadArgs, gc.HasLen, 1)
   301  	c.Assert(downloadArgs[0], gc.FitsTypeOf, downloader.Request{})
   302  	downloadRequest := downloadArgs[0].(downloader.Request)
   303  	c.Assert(downloadRequest.Abort, gc.NotNil)
   304  	c.Assert(downloadRequest.Verify, gc.NotNil)
   305  
   306  	// fakeClient.Charm returns the SHA256 sum of fakeCharmContent.
   307  	fakeCharmPath := filepath.Join(c.MkDir(), "fake.charm")
   308  	err = os.WriteFile(fakeCharmPath, fakeCharmContent, 0644)
   309  	c.Assert(err, jc.ErrorIsNil)
   310  	f, err := os.Open(fakeCharmPath)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  	defer f.Close()
   313  	err = downloadRequest.Verify(f)
   314  	c.Assert(err, jc.ErrorIsNil)
   315  
   316  	downloadRequest.Abort = nil
   317  	downloadRequest.Verify = nil
   318  	agentDir := filepath.Join(s.config.DataDir, "agents", "application-gitlab")
   319  	c.Assert(
   320  		downloadRequest,
   321  		jc.DeepEquals,
   322  		downloader.Request{
   323  			ArchiveSha256: fakeCharmSHA256,
   324  			URL:           &url.URL{Scheme: "ch", Opaque: "gitlab-1"},
   325  			TargetDir:     filepath.Join(agentDir, "state", "bundles", "downloads"),
   326  		},
   327  	)
   328  
   329  	// The download directory should have been removed.
   330  	_, err = os.Stat(downloadRequest.TargetDir)
   331  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   332  
   333  	// The charm archive should have been unpacked into <data-dir>/charm.
   334  	charmDir := filepath.Join(agentDir, "charm")
   335  	_, err = os.Stat(filepath.Join(charmDir, "metadata.yaml"))
   336  	c.Assert(err, jc.ErrorIsNil)
   337  
   338  }
   339  
   340  func (s *WorkerSuite) assertUniterStarted(c *gc.C) worker.Worker {
   341  	ch := make(chan struct{})
   342  	s.config.StartUniterFunc = func(runner *worker.Runner, params *uniter.UniterParams) error {
   343  		defer close(ch)
   344  		c.Assert(params.UnitTag.Id(), gc.Equals, "gitlab/0")
   345  		return nil
   346  	}
   347  
   348  	w, err := caasoperator.NewWorker(s.config)
   349  	c.Assert(err, jc.ErrorIsNil)
   350  
   351  	select {
   352  	case s.appChanges <- struct{}{}:
   353  	case <-time.After(coretesting.LongWait):
   354  		c.Fatal("timed out sending application change")
   355  	}
   356  	select {
   357  	case s.unitsChanges <- []string{"gitlab/0"}:
   358  	case <-time.After(coretesting.LongWait):
   359  		c.Fatal("timed out sending unit change")
   360  	}
   361  
   362  	select {
   363  	case <-ch:
   364  	case <-time.After(coretesting.ShortWait):
   365  		c.Fatal("timed out waiting for start uniter to be called for unit gitlab/0")
   366  	}
   367  	return w
   368  }
   369  
   370  func (s *WorkerSuite) TestWorkerSetsStatus(c *gc.C) {
   371  	w, err := caasoperator.NewWorker(s.config)
   372  	c.Assert(err, jc.ErrorIsNil)
   373  	defer workertest.CleanKill(c, w)
   374  
   375  	for attempt := coretesting.LongAttempt.Start(); attempt.Next(); {
   376  		if len(s.client.Calls()) == 7 {
   377  			break
   378  		}
   379  	}
   380  	s.client.CheckCallNames(c, "Charm", "SetStatus", "SetVersion", "WatchUnits", "WatchContainerStart", "SetStatus", "Watch")
   381  	s.client.CheckCall(c, 1, "SetStatus", "gitlab", status.Maintenance, "downloading charm (ch:gitlab-1)", map[string]interface{}(nil))
   382  }
   383  
   384  func (s *WorkerSuite) TestWatcherFailureStopsWorker(c *gc.C) {
   385  	w, err := caasoperator.NewWorker(s.config)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	defer workertest.DirtyKill(c, w)
   388  
   389  	s.client.unitsWatcher.KillErr(errors.New("splat"))
   390  	err = workertest.CheckKilled(c, w)
   391  	c.Assert(err, gc.ErrorMatches, "splat")
   392  }
   393  
   394  func (s *WorkerSuite) TestRemovedUnit(c *gc.C) {
   395  	w := s.assertUniterStarted(c)
   396  	defer workertest.CleanKill(c, w)
   397  
   398  	s.client.ResetCalls()
   399  	s.client.life = life.Dead
   400  	select {
   401  	case s.unitsChanges <- []string{"gitlab/0"}:
   402  	case <-time.After(coretesting.LongWait):
   403  		c.Fatal("timed out sending unit change")
   404  	}
   405  
   406  	select {
   407  	case <-s.unitRemoved:
   408  	case <-time.After(coretesting.LongWait):
   409  		c.Fatal("timed out waiting for unit to be removed")
   410  	}
   411  	s.client.CheckCallNames(c, "Life", "RemoveUnit")
   412  	s.client.CheckCall(c, 0, "Life", "gitlab/0")
   413  	s.client.CheckCall(c, 1, "RemoveUnit", "gitlab/0")
   414  }
   415  
   416  func (s *WorkerSuite) TestRemovedApplication(c *gc.C) {
   417  	s.client.SetErrors(errors.NotFoundf("app"))
   418  	w, err := caasoperator.NewWorker(s.config)
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	defer workertest.DirtyKill(c, w)
   421  
   422  	err = workertest.CheckKilled(c, w)
   423  	c.Assert(err, gc.ErrorMatches, "agent should be terminated")
   424  }
   425  
   426  func (s *WorkerSuite) TestMakeAgentSymlinks(c *gc.C) {
   427  	w, err := caasoperator.NewWorker(s.config)
   428  	c.Assert(err, jc.ErrorIsNil)
   429  	defer workertest.CleanKill(c, w)
   430  
   431  	unitTag := names.NewUnitTag("gitlab/0")
   432  	op := w.(*caasoperator.CaasOperator)
   433  	unitDir := filepath.Join(op.GetDataDir(), "agents", unitTag.String())
   434  	err = os.MkdirAll(unitDir, 0755)
   435  	c.Assert(err, jc.ErrorIsNil)
   436  
   437  	unitCharmLegacySymlink := filepath.Join(unitDir, "charm")
   438  	fakeAppDir := c.MkDir()
   439  	err = symlink.New(fakeAppDir, unitCharmLegacySymlink)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	assertSymlinkExist(c, unitCharmLegacySymlink)
   442  
   443  	err = op.MakeAgentSymlinks(unitTag)
   444  	c.Assert(err, jc.ErrorIsNil)
   445  	assertSymlinkNotExist(c, unitCharmLegacySymlink)
   446  }
   447  
   448  func assertSymlinkExist(c *gc.C, path string) {
   449  	symlinkExists, err := symlink.IsSymlink(path)
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	c.Assert(symlinkExists, jc.IsTrue)
   452  }
   453  
   454  func assertSymlinkNotExist(c *gc.C, path string) {
   455  	_, err := symlink.IsSymlink(path)
   456  	c.Assert(errors.Cause(err), jc.Satisfies, os.IsNotExist)
   457  }
   458  
   459  func (s *WorkerSuite) TestContainerStart(c *gc.C) {
   460  	uniterStarted := make(chan struct{})
   461  	uniterGotRunning := make(chan struct{})
   462  	s.mockExecutor.status = exec.Status{
   463  		PodName: "gitlab-ffff",
   464  		ContainerStatus: []exec.ContainerStatus{{
   465  			Name:    "default",
   466  			Running: true,
   467  		}},
   468  	}
   469  
   470  	s.config.StartUniterFunc = func(runner *worker.Runner, params *uniter.UniterParams) error {
   471  		go func() {
   472  			close(uniterStarted)
   473  			c.Assert(params.UnitTag.Id(), gc.Equals, "gitlab/0")
   474  			c.Assert(params.NewRemoteRunnerExecutor, gc.NotNil)
   475  			select {
   476  			case <-params.ContainerRunningStatusChannel:
   477  			case <-time.After(coretesting.LongWait):
   478  				c.Fatal("timed out sending application change")
   479  			}
   480  			running, err := params.ContainerRunningStatusFunc("gitlab-ffff")
   481  			c.Assert(err, gc.IsNil)
   482  			c.Assert(running, jc.DeepEquals, &remotestate.ContainerRunningStatus{
   483  				PodName: "gitlab-ffff",
   484  				Running: true,
   485  			})
   486  			close(uniterGotRunning)
   487  		}()
   488  		return nil
   489  	}
   490  
   491  	w, err := caasoperator.NewWorker(s.config)
   492  	c.Assert(err, jc.ErrorIsNil)
   493  	defer workertest.CleanKill(c, w)
   494  
   495  	select {
   496  	case s.appChanges <- struct{}{}:
   497  	case <-time.After(coretesting.LongWait):
   498  		c.Fatal("timed out sending application change")
   499  	}
   500  	select {
   501  	case s.unitsChanges <- []string{"gitlab/0"}:
   502  	case <-time.After(coretesting.LongWait):
   503  		c.Fatal("timed out sending unit change")
   504  	}
   505  	select {
   506  	case <-s.appWatched:
   507  	case <-time.After(coretesting.LongWait):
   508  		c.Fatal("timed out waiting for application to be watched")
   509  	}
   510  	select {
   511  	case <-uniterStarted:
   512  	case <-time.After(coretesting.LongWait):
   513  		c.Fatalf("timeout while waiting for uniter to start")
   514  	}
   515  	select {
   516  	case s.containerChanges <- []string{"gitlab/0"}:
   517  	case <-time.After(coretesting.LongWait):
   518  		c.Fatalf("timeout while waiting for uniter to start")
   519  	}
   520  	select {
   521  	case <-uniterGotRunning:
   522  	case <-time.After(coretesting.LongWait):
   523  		c.Fatalf("timeout while waiting for uniter to receive running status")
   524  	}
   525  
   526  	s.client.CheckCallNames(c, "Charm", "SetStatus", "SetVersion", "WatchUnits", "WatchContainerStart", "SetStatus", "Watch", "Charm", "Life")
   527  	s.client.CheckCall(c, 0, "Charm", "gitlab")
   528  	s.client.CheckCall(c, 2, "SetVersion", "gitlab", coretesting.CurrentVersion())
   529  	s.client.CheckCall(c, 3, "WatchUnits", "gitlab")
   530  	s.client.CheckCall(c, 4, "WatchContainerStart", "gitlab", "(?:juju-pod-init|)")
   531  	s.client.CheckCall(c, 6, "Watch", "gitlab")
   532  }
   533  
   534  func (s *WorkerSuite) TestOperatorNoWaitContainerStart(c *gc.C) {
   535  	uniterStarted := make(chan struct{})
   536  	s.config.StartUniterFunc = func(runner *worker.Runner, params *uniter.UniterParams) error {
   537  		go func() {
   538  			close(uniterStarted)
   539  			c.Assert(params.UnitTag.Id(), gc.Equals, "gitlab/0")
   540  			c.Assert(params.ContainerRunningStatusChannel, gc.IsNil)
   541  		}()
   542  		return nil
   543  	}
   544  	s.client.mode = caas.ModeOperator
   545  
   546  	w, err := caasoperator.NewWorker(s.config)
   547  	c.Assert(err, jc.ErrorIsNil)
   548  	defer workertest.CleanKill(c, w)
   549  
   550  	select {
   551  	case s.appChanges <- struct{}{}:
   552  	case <-time.After(coretesting.LongWait):
   553  		c.Fatal("timed out sending application change")
   554  	}
   555  	select {
   556  	case s.unitsChanges <- []string{"gitlab/0"}:
   557  	case <-time.After(coretesting.LongWait):
   558  		c.Fatal("timed out sending unit change")
   559  	}
   560  	select {
   561  	case <-s.appWatched:
   562  	case <-time.After(coretesting.LongWait):
   563  		c.Fatal("timed out waiting for application to be watched")
   564  	}
   565  	select {
   566  	case <-uniterStarted:
   567  	case <-time.After(coretesting.LongWait):
   568  		c.Fatalf("timeout while waiting for uniter to start")
   569  	}
   570  
   571  	s.client.CheckCallNames(c, "Charm", "SetStatus", "SetVersion", "WatchUnits", "SetStatus", "Watch", "Charm", "Life")
   572  	s.client.CheckCall(c, 0, "Charm", "gitlab")
   573  	s.client.CheckCall(c, 2, "SetVersion", "gitlab", coretesting.CurrentVersion())
   574  	s.client.CheckCall(c, 3, "WatchUnits", "gitlab")
   575  	s.client.CheckCall(c, 5, "Watch", "gitlab")
   576  }