github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/caasfirewaller/worker_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasfirewaller_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/charm/v12"
    10  	"github.com/juju/clock"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/retry"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/worker/v3/workertest"
    17  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/api/common/charms"
    20  	"github.com/juju/juju/core/config"
    21  	"github.com/juju/juju/core/life"
    22  	"github.com/juju/juju/core/watcher/watchertest"
    23  	coretesting "github.com/juju/juju/testing"
    24  	"github.com/juju/juju/worker/caasfirewaller"
    25  )
    26  
    27  type WorkerSuite struct {
    28  	testing.IsolationSuite
    29  
    30  	config            caasfirewaller.Config
    31  	applicationGetter mockApplicationGetter
    32  	serviceExposer    mockServiceExposer
    33  	lifeGetter        mockLifeGetter
    34  	charmGetter       mockCharmGetter
    35  
    36  	applicationChanges chan []string
    37  	appExposedChange   chan struct{}
    38  	serviceExposed     chan struct{}
    39  	serviceUnexposed   chan struct{}
    40  }
    41  
    42  var _ = gc.Suite(&WorkerSuite{})
    43  
    44  func (s *WorkerSuite) SetUpTest(c *gc.C) {
    45  	s.IsolationSuite.SetUpTest(c)
    46  
    47  	s.applicationChanges = make(chan []string)
    48  	s.appExposedChange = make(chan struct{})
    49  	s.serviceExposed = make(chan struct{})
    50  	s.serviceUnexposed = make(chan struct{})
    51  
    52  	s.applicationGetter = mockApplicationGetter{
    53  		allWatcher: watchertest.NewMockStringsWatcher(s.applicationChanges),
    54  		appWatcher: watchertest.NewMockNotifyWatcher(s.appExposedChange),
    55  	}
    56  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.applicationGetter.allWatcher) })
    57  
    58  	s.lifeGetter = mockLifeGetter{
    59  		life: life.Alive,
    60  	}
    61  	s.charmGetter = mockCharmGetter{
    62  		charmInfo: &charms.CharmInfo{
    63  			Manifest: &charm.Manifest{},
    64  			Meta:     &charm.Meta{},
    65  		},
    66  	}
    67  	s.serviceExposer = mockServiceExposer{
    68  		exposed:   s.serviceExposed,
    69  		unexposed: s.serviceUnexposed,
    70  	}
    71  
    72  	s.config = caasfirewaller.Config{
    73  		ControllerUUID:    coretesting.ControllerTag.Id(),
    74  		ModelUUID:         coretesting.ModelTag.Id(),
    75  		ApplicationGetter: &s.applicationGetter,
    76  		ServiceExposer:    &s.serviceExposer,
    77  		LifeGetter:        &s.lifeGetter,
    78  		CharmGetter:       &s.charmGetter,
    79  		Logger:            loggo.GetLogger("test"),
    80  	}
    81  }
    82  
    83  func (s *WorkerSuite) sendApplicationExposedChange(c *gc.C) {
    84  	select {
    85  	case s.appExposedChange <- struct{}{}:
    86  	case <-time.After(coretesting.LongWait):
    87  		c.Fatal("timed out sending application exposed change")
    88  	}
    89  }
    90  
    91  func (s *WorkerSuite) TestValidateConfig(c *gc.C) {
    92  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
    93  		config.ControllerUUID = ""
    94  	}, `missing ControllerUUID not valid`)
    95  
    96  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
    97  		config.ModelUUID = ""
    98  	}, `missing ModelUUID not valid`)
    99  
   100  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
   101  		config.ApplicationGetter = nil
   102  	}, `missing ApplicationGetter not valid`)
   103  
   104  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
   105  		config.ServiceExposer = nil
   106  	}, `missing ServiceExposer not valid`)
   107  
   108  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
   109  		config.LifeGetter = nil
   110  	}, `missing LifeGetter not valid`)
   111  
   112  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
   113  		config.CharmGetter = nil
   114  	}, `missing CharmGetter not valid`)
   115  
   116  	s.testValidateConfig(c, func(config *caasfirewaller.Config) {
   117  		config.Logger = nil
   118  	}, `missing Logger not valid`)
   119  }
   120  
   121  func (s *WorkerSuite) testValidateConfig(c *gc.C, f func(*caasfirewaller.Config), expect string) {
   122  	config := s.config
   123  	f(&config)
   124  	w, err := caasfirewaller.NewWorker(config)
   125  	if err == nil {
   126  		workertest.DirtyKill(c, w)
   127  	}
   128  	c.Check(err, gc.ErrorMatches, expect)
   129  }
   130  
   131  func (s *WorkerSuite) TestStartStop(c *gc.C) {
   132  	w, err := caasfirewaller.NewWorker(s.config)
   133  	c.Assert(err, jc.ErrorIsNil)
   134  	workertest.CheckAlive(c, w)
   135  	workertest.CleanKill(c, w)
   136  }
   137  
   138  func (s *WorkerSuite) sendApplicationChange(c *gc.C, appName string) {
   139  	select {
   140  	case s.applicationChanges <- []string{appName}:
   141  	case <-time.After(coretesting.LongWait):
   142  		c.Fatal("timed out sending applications change")
   143  	}
   144  }
   145  
   146  func (s *WorkerSuite) TestExposedChange(c *gc.C) {
   147  	w, err := caasfirewaller.NewWorker(s.config)
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	defer workertest.CleanKill(c, w)
   150  
   151  	s.sendApplicationChange(c, "gitlab")
   152  
   153  	s.sendApplicationExposedChange(c)
   154  	// The last known state on start up was unexposed
   155  	// so we first call Unexpose().
   156  	select {
   157  	case <-s.serviceUnexposed:
   158  	case <-time.After(coretesting.LongWait):
   159  		c.Fatal("timed out waiting for service to be unexposed")
   160  	}
   161  	select {
   162  	case <-s.serviceExposed:
   163  		c.Fatal("service exposed unexpectedly")
   164  	case <-time.After(coretesting.ShortWait):
   165  	}
   166  
   167  	s.applicationGetter.exposed = true
   168  	s.sendApplicationExposedChange(c)
   169  	select {
   170  	case <-s.serviceExposed:
   171  	case <-time.After(coretesting.LongWait):
   172  		c.Fatal("timed out waiting for service to be exposed")
   173  	}
   174  	s.serviceExposer.CheckCallNames(c, "UnexposeService", "ExposeService")
   175  	s.serviceExposer.CheckCall(c, 1, "ExposeService", "gitlab",
   176  		map[string]string{
   177  			"juju-controller-uuid": coretesting.ControllerTag.Id(),
   178  			"juju-model-uuid":      coretesting.ModelTag.Id()},
   179  		config.ConfigAttributes{"juju-external-hostname": "exthost"})
   180  }
   181  
   182  func (s *WorkerSuite) TestUnexposedChange(c *gc.C) {
   183  	w, err := caasfirewaller.NewWorker(s.config)
   184  	c.Assert(err, jc.ErrorIsNil)
   185  	defer workertest.CleanKill(c, w)
   186  
   187  	s.sendApplicationChange(c, "gitlab")
   188  
   189  	s.applicationGetter.exposed = true
   190  	s.sendApplicationExposedChange(c)
   191  	// The last known state on start up was exposed
   192  	// so we first call Expose().
   193  	select {
   194  	case <-s.serviceExposed:
   195  	case <-time.After(coretesting.LongWait):
   196  		c.Fatal("timed out waiting for service to be exposed")
   197  	}
   198  	select {
   199  	case <-s.serviceUnexposed:
   200  		c.Fatal("service unexposed unexpectedly")
   201  	case <-time.After(coretesting.ShortWait):
   202  	}
   203  
   204  	s.applicationGetter.exposed = false
   205  	s.sendApplicationExposedChange(c)
   206  	select {
   207  	case <-s.serviceUnexposed:
   208  	case <-time.After(coretesting.LongWait):
   209  		c.Fatal("timed out waiting for service to be unexposed")
   210  	}
   211  }
   212  
   213  func (s *WorkerSuite) TestWatchApplicationDead(c *gc.C) {
   214  	w, err := caasfirewaller.NewWorker(s.config)
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	defer workertest.CleanKill(c, w)
   217  
   218  	s.lifeGetter.life = life.Dead
   219  	s.sendApplicationChange(c, "gitlab")
   220  
   221  	select {
   222  	case s.appExposedChange <- struct{}{}:
   223  		c.Fatal("unexpected watch for app exposed")
   224  	case <-time.After(coretesting.ShortWait):
   225  	}
   226  
   227  	workertest.CleanKill(c, w)
   228  }
   229  
   230  func (s *WorkerSuite) TestRemoveApplicationStopsWatchingApplication(c *gc.C) {
   231  	// Set up the errors before triggering any events to avoid racing
   232  	// with the worker loop. First time around the loop the
   233  	// application's alive, then it's gone.
   234  	s.lifeGetter.SetErrors(nil, errors.NotFoundf("application"))
   235  
   236  	w, err := caasfirewaller.NewWorker(s.config)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	defer workertest.CleanKill(c, w)
   239  
   240  	s.sendApplicationChange(c, "gitlab")
   241  	s.sendApplicationChange(c, "gitlab")
   242  
   243  	err = workertest.CheckKilled(c, s.applicationGetter.appWatcher)
   244  	c.Assert(err, jc.ErrorIsNil)
   245  }
   246  
   247  func (s *WorkerSuite) TestRemoveApplicationStopsWorker(c *gc.C) {
   248  	// Set up the errors before triggering any events to avoid racing
   249  	// with the worker loop. First time around the loop the
   250  	// application's alive, then it's gone.
   251  	s.applicationGetter.SetErrors(nil, nil, errors.NotFoundf("application"))
   252  
   253  	w, err := caasfirewaller.NewWorker(s.config)
   254  	c.Assert(err, jc.ErrorIsNil)
   255  	defer workertest.CleanKill(c, w)
   256  
   257  	s.sendApplicationChange(c, "gitlab")
   258  
   259  	s.applicationGetter.exposed = true
   260  	s.sendApplicationExposedChange(c)
   261  	select {
   262  	case <-s.serviceExposed:
   263  		c.Fatal("removed application should not be exposed")
   264  	case <-time.After(coretesting.ShortWait):
   265  	}
   266  }
   267  
   268  func (s *WorkerSuite) TestWatcherErrorStopsWorker(c *gc.C) {
   269  	w, err := caasfirewaller.NewWorker(s.config)
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	defer workertest.DirtyKill(c, w)
   272  
   273  	s.sendApplicationChange(c, "gitlab")
   274  
   275  	s.applicationGetter.appWatcher.KillErr(errors.New("splat"))
   276  	_ = workertest.CheckKilled(c, s.applicationGetter.appWatcher)
   277  	_ = workertest.CheckKilled(c, s.applicationGetter.allWatcher)
   278  	err = workertest.CheckKilled(c, w)
   279  	c.Assert(err, gc.ErrorMatches, "splat")
   280  }
   281  
   282  func (s *WorkerSuite) TestV2CharmSkipProcessing(c *gc.C) {
   283  	s.charmGetter.charmInfo.Manifest = &charm.Manifest{Bases: []charm.Base{{}}}
   284  	s.charmGetter.charmInfo.Meta = &charm.Meta{}
   285  
   286  	w, err := caasfirewaller.NewWorker(s.config)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  
   289  	s.sendApplicationChange(c, "gitlab")
   290  	s.waitCharmGetterCalls(c, "ApplicationCharmInfo")
   291  
   292  	workertest.CleanKill(c, w)
   293  
   294  	s.expectNoLifeGetterCalls(c)
   295  }
   296  
   297  func (s *WorkerSuite) TestCharmNotFound(c *gc.C) {
   298  	w, err := caasfirewaller.NewWorker(s.config)
   299  	c.Assert(err, jc.ErrorIsNil)
   300  
   301  	s.charmGetter.charmInfo = nil
   302  
   303  	s.sendApplicationChange(c, "gitlab")
   304  	s.waitCharmGetterCalls(c, "ApplicationCharmInfo")
   305  
   306  	workertest.CleanKill(c, w)
   307  
   308  	s.expectNoLifeGetterCalls(c)
   309  }
   310  
   311  func (s *WorkerSuite) TestCharmChangesToV2(c *gc.C) {
   312  	w, err := caasfirewaller.NewWorker(s.config)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	defer workertest.CleanKill(c, w)
   315  
   316  	s.sendApplicationChange(c, "gitlab")
   317  	s.waitCharmGetterCalls(c, "ApplicationCharmInfo")
   318  	s.waitLifeGetterCalls(c, "Life")
   319  
   320  	s.charmGetter.charmInfo.Manifest = &charm.Manifest{Bases: []charm.Base{{}}}
   321  	s.charmGetter.charmInfo.Meta = &charm.Meta{}
   322  	s.sendApplicationExposedChange(c)
   323  	s.waitCharmGetterCalls(c, "ApplicationCharmInfo")
   324  
   325  	err = workertest.CheckKilled(c, s.applicationGetter.appWatcher)
   326  	c.Assert(err, jc.ErrorIsNil)
   327  }
   328  
   329  func (s *WorkerSuite) waitCharmGetterCalls(c *gc.C, names ...string) {
   330  	waitStubCalls(c, &s.charmGetter, names...)
   331  }
   332  
   333  func (s *WorkerSuite) waitLifeGetterCalls(c *gc.C, names ...string) {
   334  	waitStubCalls(c, &s.lifeGetter, names...)
   335  }
   336  
   337  type waitStub interface {
   338  	Calls() []testing.StubCall
   339  	CheckCallNames(c *gc.C, expected ...string) bool
   340  	ResetCalls()
   341  }
   342  
   343  func waitStubCalls(c *gc.C, stub waitStub, names ...string) {
   344  	retryCallArgs := coretesting.LongRetryStrategy
   345  	retryCallArgs.Func = func() error {
   346  		if len(stub.Calls()) >= len(names) {
   347  			return nil
   348  		}
   349  		return errors.Errorf("Not enough calls yet")
   350  	}
   351  	err := retry.Call(retryCallArgs)
   352  	c.Assert(err, jc.ErrorIsNil)
   353  
   354  	stub.CheckCallNames(c, names...)
   355  	stub.ResetCalls()
   356  }
   357  
   358  func (s *WorkerSuite) expectNoLifeGetterCalls(c *gc.C) {
   359  	totalDuration := clock.WallClock.After(coretesting.ShortWait)
   360  	for {
   361  		select {
   362  		case <-clock.WallClock.After(10 * time.Millisecond):
   363  			if len(s.lifeGetter.Calls()) > 0 {
   364  				c.Fatalf("unexpected lifegetter call")
   365  			}
   366  		case <-totalDuration:
   367  			return
   368  		}
   369  	}
   370  }