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

     1  // Copyright 2020 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasapplicationprovisioner_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/clock/testclock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/worker/v3"
    16  	"github.com/juju/worker/v3/workertest"
    17  	"go.uber.org/mock/gomock"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/caas"
    21  	caasmocks "github.com/juju/juju/caas/mocks"
    22  	"github.com/juju/juju/core/life"
    23  	"github.com/juju/juju/core/status"
    24  	"github.com/juju/juju/core/watcher"
    25  	"github.com/juju/juju/core/watcher/watchertest"
    26  	"github.com/juju/juju/rpc/params"
    27  	coretesting "github.com/juju/juju/testing"
    28  	"github.com/juju/juju/worker/caasapplicationprovisioner"
    29  	"github.com/juju/juju/worker/caasapplicationprovisioner/mocks"
    30  )
    31  
    32  var _ = gc.Suite(&ApplicationWorkerSuite{})
    33  
    34  type ApplicationWorkerSuite struct {
    35  	coretesting.BaseSuite
    36  
    37  	modelTag names.ModelTag
    38  	logger   loggo.Logger
    39  }
    40  
    41  func (s *ApplicationWorkerSuite) SetUpTest(c *gc.C) {
    42  	s.BaseSuite.SetUpTest(c)
    43  
    44  	s.modelTag = names.NewModelTag("ffffffff-ffff-ffff-ffff-ffffffffffff")
    45  	s.logger = loggo.GetLogger("test")
    46  }
    47  
    48  func (s *ApplicationWorkerSuite) waitDone(c *gc.C, done chan struct{}) {
    49  	select {
    50  	case <-done:
    51  	case <-time.After(coretesting.LongWait):
    52  		c.Errorf("timed out waiting for worker")
    53  	}
    54  }
    55  
    56  func (s *ApplicationWorkerSuite) startAppWorker(
    57  	c *gc.C,
    58  	clk clock.Clock,
    59  	facade caasapplicationprovisioner.CAASProvisionerFacade,
    60  	broker caasapplicationprovisioner.CAASBroker,
    61  	unitFacade caasapplicationprovisioner.CAASUnitProvisionerFacade,
    62  	ops caasapplicationprovisioner.ApplicationOps,
    63  	statusOnly bool,
    64  ) worker.Worker {
    65  	config := caasapplicationprovisioner.AppWorkerConfig{
    66  		Name:       "test",
    67  		Facade:     facade,
    68  		Broker:     broker,
    69  		ModelTag:   s.modelTag,
    70  		Clock:      clk,
    71  		Logger:     s.logger,
    72  		UnitFacade: unitFacade,
    73  		Ops:        ops,
    74  		StatusOnly: statusOnly,
    75  	}
    76  	startFunc := caasapplicationprovisioner.NewAppWorker(config)
    77  	c.Assert(startFunc, gc.NotNil)
    78  	appWorker, err := startFunc()
    79  	c.Assert(err, jc.ErrorIsNil)
    80  	c.Assert(appWorker, gc.NotNil)
    81  	return appWorker
    82  }
    83  
    84  func (s *ApplicationWorkerSuite) TestLifeNotFound(c *gc.C) {
    85  	ctrl := gomock.NewController(c)
    86  	defer ctrl.Finish()
    87  
    88  	broker := mocks.NewMockCAASBroker(ctrl)
    89  	brokerApp := caasmocks.NewMockApplication(ctrl)
    90  	facade := mocks.NewMockCAASProvisionerFacade(ctrl)
    91  	ops := mocks.NewMockApplicationOps(ctrl)
    92  	done := make(chan struct{})
    93  
    94  	gomock.InOrder(
    95  		broker.EXPECT().Application("test", caas.DeploymentStateful).Return(brokerApp),
    96  		facade.EXPECT().Life("test").DoAndReturn(func(appName string) (life.Value, error) {
    97  			close(done)
    98  			return "", errors.NotFoundf("test charm")
    99  		}),
   100  	)
   101  	appWorker := s.startAppWorker(c, nil, facade, broker, nil, ops, false)
   102  
   103  	s.waitDone(c, done)
   104  	workertest.CleanKill(c, appWorker)
   105  }
   106  
   107  func (s *ApplicationWorkerSuite) TestLifeDead(c *gc.C) {
   108  	ctrl := gomock.NewController(c)
   109  	defer ctrl.Finish()
   110  
   111  	broker := mocks.NewMockCAASBroker(ctrl)
   112  	app := caasmocks.NewMockApplication(ctrl)
   113  	facade := mocks.NewMockCAASProvisionerFacade(ctrl)
   114  	unitFacade := mocks.NewMockCAASUnitProvisionerFacade(ctrl)
   115  	ops := mocks.NewMockApplicationOps(ctrl)
   116  	clk := testclock.NewDilatedWallClock(time.Millisecond)
   117  
   118  	done := make(chan struct{})
   119  
   120  	gomock.InOrder(
   121  		broker.EXPECT().Application("test", caas.DeploymentStateful).Return(app),
   122  		facade.EXPECT().Life("test").Return(life.Dead, nil),
   123  		ops.EXPECT().AppDying("test", app, life.Dead, facade, unitFacade, s.logger).Return(nil),
   124  		ops.EXPECT().AppDead("test", app, broker, facade, unitFacade, clk, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) error {
   125  			close(done)
   126  			return nil
   127  		}),
   128  	)
   129  	appWorker := s.startAppWorker(c, clk, facade, broker, unitFacade, ops, false)
   130  
   131  	s.waitDone(c, done)
   132  	workertest.CleanKill(c, appWorker)
   133  }
   134  
   135  func (s *ApplicationWorkerSuite) TestUpgradePodSpec(c *gc.C) {
   136  	ctrl := gomock.NewController(c)
   137  	defer ctrl.Finish()
   138  
   139  	broker := mocks.NewMockCAASBroker(ctrl)
   140  	brokerApp := caasmocks.NewMockApplication(ctrl)
   141  	facade := mocks.NewMockCAASProvisionerFacade(ctrl)
   142  	ops := mocks.NewMockApplicationOps(ctrl)
   143  	done := make(chan struct{})
   144  
   145  	clk := testclock.NewClock(time.Time{})
   146  	gomock.InOrder(
   147  		broker.EXPECT().Application("test", caas.DeploymentStateful).Return(brokerApp),
   148  		facade.EXPECT().Life("test").Return(life.Alive, nil),
   149  
   150  		// Verify charm is v2
   151  		ops.EXPECT().VerifyCharmUpgraded("test", gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil),
   152  
   153  		// Operator delete loop (with a retry)
   154  		ops.EXPECT().UpgradePodSpec("test", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
   155  
   156  		// Make SetPassword return an error to exit early (we've tested what
   157  		// we want to above).
   158  		facade.EXPECT().SetPassword("test", gomock.Any()).DoAndReturn(func(appName, password string) error {
   159  			close(done)
   160  			return errors.New("exit early error")
   161  		}),
   162  	)
   163  
   164  	appWorker := s.startAppWorker(c, clk, facade, broker, nil, ops, false)
   165  
   166  	s.waitDone(c, done)
   167  	workertest.DirtyKill(c, appWorker)
   168  }
   169  
   170  func (s *ApplicationWorkerSuite) TestWorker(c *gc.C) {
   171  	ctrl := gomock.NewController(c)
   172  	defer ctrl.Finish()
   173  
   174  	broker := mocks.NewMockCAASBroker(ctrl)
   175  	app := caasmocks.NewMockApplication(ctrl)
   176  	facade := mocks.NewMockCAASProvisionerFacade(ctrl)
   177  	unitFacade := mocks.NewMockCAASUnitProvisionerFacade(ctrl)
   178  	ops := mocks.NewMockApplicationOps(ctrl)
   179  	done := make(chan struct{})
   180  
   181  	clk := testclock.NewDilatedWallClock(time.Millisecond)
   182  
   183  	scaleChan := make(chan struct{}, 1)
   184  	trustChan := make(chan []string, 1)
   185  	provisioningInfoChan := make(chan struct{}, 1)
   186  	appUnitsChan := make(chan []string, 1)
   187  	appChan := make(chan struct{}, 1)
   188  	appReplicasChan := make(chan struct{}, 1)
   189  
   190  	ops.EXPECT().RefreshApplicationStatus("test", app, gomock.Any(), facade, s.logger).Return(nil).AnyTimes()
   191  
   192  	gomock.InOrder(
   193  		broker.EXPECT().Application("test", caas.DeploymentStateful).Return(app),
   194  		facade.EXPECT().Life("test").Return(life.Alive, nil),
   195  
   196  		ops.EXPECT().VerifyCharmUpgraded("test", gomock.Any(), gomock.Any(), gomock.Any()).Return(false, nil),
   197  		ops.EXPECT().UpgradePodSpec("test", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil),
   198  
   199  		facade.EXPECT().SetPassword("test", gomock.Any()).Return(nil),
   200  
   201  		unitFacade.EXPECT().WatchApplicationScale("test").Return(watchertest.NewMockNotifyWatcher(scaleChan), nil),
   202  		unitFacade.EXPECT().WatchApplicationTrustHash("test").Return(watchertest.NewMockStringsWatcher(trustChan), nil),
   203  		facade.EXPECT().WatchUnits("test").Return(watchertest.NewMockStringsWatcher(appUnitsChan), nil),
   204  
   205  		// handleChange
   206  		facade.EXPECT().Life("test").Return(life.Alive, nil),
   207  		facade.EXPECT().ProvisioningState("test").Return(nil, nil),
   208  		facade.EXPECT().WatchProvisioningInfo("test").Return(watchertest.NewMockNotifyWatcher(provisioningInfoChan), nil),
   209  		ops.EXPECT().AppAlive("test", app, gomock.Any(), gomock.Any(), facade, clk, s.logger).Return(nil),
   210  		app.EXPECT().Watch().Return(watchertest.NewMockNotifyWatcher(appChan), nil),
   211  		app.EXPECT().WatchReplicas().DoAndReturn(func() (watcher.NotifyWatcher, error) {
   212  			scaleChan <- struct{}{}
   213  			return watchertest.NewMockNotifyWatcher(appReplicasChan), nil
   214  		}),
   215  
   216  		// scaleChan fired
   217  		ops.EXPECT().EnsureScale("test", app, life.Alive, facade, unitFacade, s.logger).Return(errors.NotFound),
   218  		ops.EXPECT().EnsureScale("test", app, life.Alive, facade, unitFacade, s.logger).Return(errors.ConstError("try again")),
   219  		ops.EXPECT().EnsureScale("test", app, life.Alive, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _ any) error {
   220  			trustChan <- nil
   221  			return nil
   222  		}),
   223  
   224  		// trustChan fired
   225  		ops.EXPECT().EnsureTrust("test", app, unitFacade, s.logger).Return(errors.NotFound),
   226  		ops.EXPECT().EnsureTrust("test", app, unitFacade, s.logger).DoAndReturn(func(_, _, _, _ any) error {
   227  			appUnitsChan <- nil
   228  			return nil
   229  		}),
   230  
   231  		// appUnitsChan fired
   232  		ops.EXPECT().ReconcileDeadUnitScale("test", app, facade, s.logger).Return(errors.NotFound),
   233  		ops.EXPECT().ReconcileDeadUnitScale("test", app, facade, s.logger).Return(errors.ConstError("try again")),
   234  		ops.EXPECT().ReconcileDeadUnitScale("test", app, facade, s.logger).DoAndReturn(func(_, _, _, _ any) error {
   235  			appChan <- struct{}{}
   236  			return nil
   237  		}),
   238  
   239  		// appChan fired
   240  		ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) {
   241  			appReplicasChan <- struct{}{}
   242  			return nil, nil
   243  		}),
   244  		// appReplicasChan fired
   245  		ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) {
   246  			provisioningInfoChan <- struct{}{}
   247  			return nil, nil
   248  		}),
   249  
   250  		// provisioningInfoChan fired
   251  		facade.EXPECT().Life("test").Return(life.Alive, nil),
   252  		ops.EXPECT().AppAlive("test", app, gomock.Any(), gomock.Any(), facade, clk, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) error {
   253  			provisioningInfoChan <- struct{}{}
   254  			return nil
   255  		}),
   256  		facade.EXPECT().Life("test").Return(life.Dying, nil),
   257  		ops.EXPECT().AppDying("test", app, life.Dying, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _ any) error {
   258  			provisioningInfoChan <- struct{}{}
   259  			return nil
   260  		}),
   261  		facade.EXPECT().Life("test").Return(life.Dead, nil),
   262  		ops.EXPECT().AppDying("test", app, life.Dead, facade, unitFacade, s.logger).Return(nil),
   263  		ops.EXPECT().AppDead("test", app, broker, facade, unitFacade, clk, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) error {
   264  			close(done)
   265  			return nil
   266  		}),
   267  	)
   268  
   269  	appWorker := s.startAppWorker(c, clk, facade, broker, unitFacade, ops, false)
   270  	s.waitDone(c, done)
   271  	workertest.CheckKill(c, appWorker)
   272  }
   273  
   274  func (s *ApplicationWorkerSuite) TestWorkerStatusOnly(c *gc.C) {
   275  	ctrl := gomock.NewController(c)
   276  	defer ctrl.Finish()
   277  
   278  	broker := mocks.NewMockCAASBroker(ctrl)
   279  	app := caasmocks.NewMockApplication(ctrl)
   280  	facade := mocks.NewMockCAASProvisionerFacade(ctrl)
   281  	unitFacade := mocks.NewMockCAASUnitProvisionerFacade(ctrl)
   282  	ops := mocks.NewMockApplicationOps(ctrl)
   283  	done := make(chan struct{})
   284  
   285  	clk := testclock.NewDilatedWallClock(time.Millisecond)
   286  
   287  	scaleChan := make(chan struct{}, 1)
   288  	trustChan := make(chan []string, 1)
   289  	provisioningInfoChan := make(chan struct{}, 1)
   290  	appUnitsChan := make(chan []string, 1)
   291  	appChan := make(chan struct{}, 1)
   292  	appReplicasChan := make(chan struct{}, 1)
   293  
   294  	ops.EXPECT().RefreshApplicationStatus("test", app, gomock.Any(), facade, s.logger).Return(nil).AnyTimes()
   295  
   296  	gomock.InOrder(
   297  		broker.EXPECT().Application("test", caas.DeploymentStateful).Return(app),
   298  		facade.EXPECT().Life("test").Return(life.Alive, nil),
   299  
   300  		unitFacade.EXPECT().WatchApplicationScale("test").Return(watchertest.NewMockNotifyWatcher(scaleChan), nil),
   301  		unitFacade.EXPECT().WatchApplicationTrustHash("test").Return(watchertest.NewMockStringsWatcher(trustChan), nil),
   302  		facade.EXPECT().WatchUnits("test").Return(watchertest.NewMockStringsWatcher(appUnitsChan), nil),
   303  
   304  		// handleChange
   305  		facade.EXPECT().Life("test").Return(life.Alive, nil),
   306  		facade.EXPECT().ProvisioningState("test").Return(&params.CAASApplicationProvisioningState{Scaling: true, ScaleTarget: 1}, nil),
   307  		facade.EXPECT().SetProvisioningState("test", params.CAASApplicationProvisioningState{}).Return(nil),
   308  		facade.EXPECT().WatchProvisioningInfo("test").Return(watchertest.NewMockNotifyWatcher(provisioningInfoChan), nil),
   309  		app.EXPECT().Watch().Return(watchertest.NewMockNotifyWatcher(appChan), nil),
   310  		app.EXPECT().WatchReplicas().DoAndReturn(func() (watcher.NotifyWatcher, error) {
   311  			appChan <- struct{}{}
   312  			return watchertest.NewMockNotifyWatcher(appReplicasChan), nil
   313  		}),
   314  
   315  		// appChan fired
   316  		ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) {
   317  			appReplicasChan <- struct{}{}
   318  			return nil, nil
   319  		}),
   320  		// appReplicasChan fired
   321  		ops.EXPECT().UpdateState("test", app, gomock.Any(), broker, facade, unitFacade, s.logger).DoAndReturn(func(_, _, _, _, _, _, _ any) (map[string]status.StatusInfo, error) {
   322  			provisioningInfoChan <- struct{}{}
   323  			return nil, nil
   324  		}),
   325  
   326  		// provisioningInfoChan fired
   327  		facade.EXPECT().Life("test").DoAndReturn(func(_ string) (life.Value, error) {
   328  			provisioningInfoChan <- struct{}{}
   329  			return life.Alive, nil
   330  		}),
   331  		facade.EXPECT().Life("test").DoAndReturn(func(_ string) (life.Value, error) {
   332  			provisioningInfoChan <- struct{}{}
   333  			return life.Dying, nil
   334  		}),
   335  		facade.EXPECT().Life("test").DoAndReturn(func(_ string) (life.Value, error) {
   336  			provisioningInfoChan <- struct{}{}
   337  			close(done)
   338  			return life.Dead, nil
   339  		}),
   340  	)
   341  
   342  	appWorker := s.startAppWorker(c, clk, facade, broker, unitFacade, ops, true)
   343  	s.waitDone(c, done)
   344  	workertest.CheckKill(c, appWorker)
   345  }