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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasoperatorprovisioner_test
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"strconv"
    12  	"time"
    13  
    14  	"github.com/juju/charm/v12"
    15  	"github.com/juju/clock/testclock"
    16  	"github.com/juju/errors"
    17  	"github.com/juju/loggo"
    18  	"github.com/juju/names/v5"
    19  	"github.com/juju/retry"
    20  	jujutesting "github.com/juju/testing"
    21  	jc "github.com/juju/testing/checkers"
    22  	"github.com/juju/version/v2"
    23  	"github.com/juju/worker/v3"
    24  	"github.com/juju/worker/v3/workertest"
    25  	gc "gopkg.in/check.v1"
    26  
    27  	"github.com/juju/juju/agent"
    28  	apicaasprovisioner "github.com/juju/juju/api/controller/caasoperatorprovisioner"
    29  	"github.com/juju/juju/caas"
    30  	"github.com/juju/juju/core/resources"
    31  	coretesting "github.com/juju/juju/testing"
    32  	"github.com/juju/juju/worker/caasoperatorprovisioner"
    33  )
    34  
    35  var _ = gc.Suite(&CAASProvisionerSuite{})
    36  
    37  type CAASProvisionerSuite struct {
    38  	coretesting.BaseSuite
    39  	stub *jujutesting.Stub
    40  
    41  	provisionerFacade *mockProvisionerFacade
    42  	caasClient        *mockBroker
    43  	agentConfig       agent.Config
    44  	clock             *testclock.Clock
    45  	modelTag          names.ModelTag
    46  }
    47  
    48  func (s *CAASProvisionerSuite) SetUpTest(c *gc.C) {
    49  	s.BaseSuite.SetUpTest(c)
    50  
    51  	s.stub = new(jujutesting.Stub)
    52  	s.provisionerFacade = newMockProvisionerFacade(s.stub)
    53  	s.caasClient = &mockBroker{}
    54  	s.agentConfig = &mockAgentConfig{}
    55  	s.modelTag = coretesting.ModelTag
    56  	s.clock = testclock.NewClock(time.Now())
    57  }
    58  
    59  func (s *CAASProvisionerSuite) waitForWorkerStubCalls(c *gc.C, expected []jujutesting.StubCall) {
    60  	waitForStubCalls(c, s.stub, expected)
    61  }
    62  
    63  func waitForStubCalls(c *gc.C, stub *jujutesting.Stub, expected []jujutesting.StubCall) {
    64  	var calls []jujutesting.StubCall
    65  	retryCallArgs := coretesting.LongRetryStrategy
    66  	retryCallArgs.Func = func() error {
    67  		calls = stub.Calls()
    68  		if reflect.DeepEqual(calls, expected) {
    69  			return nil
    70  		}
    71  		return errors.NotYetAvailablef("Calls not ready")
    72  	}
    73  	err := retry.Call(retryCallArgs)
    74  	if err != nil {
    75  		c.Fatalf("failed to see expected calls. saw: %v", calls)
    76  	}
    77  }
    78  
    79  func (s *CAASProvisionerSuite) assertWorker(c *gc.C) worker.Worker {
    80  	w, err := caasoperatorprovisioner.NewProvisionerWorker(caasoperatorprovisioner.Config{
    81  		Facade:          s.provisionerFacade,
    82  		OperatorManager: s.caasClient,
    83  		ModelTag:        s.modelTag,
    84  		AgentConfig:     s.agentConfig,
    85  		Clock:           s.clock,
    86  		Logger:          loggo.GetLogger("test"),
    87  	})
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	expected := []jujutesting.StubCall{
    90  		{"WatchApplications", nil},
    91  	}
    92  	s.waitForWorkerStubCalls(c, expected)
    93  	s.stub.ResetCalls()
    94  	return w
    95  }
    96  
    97  func (s *CAASProvisionerSuite) TestWorkerStarts(c *gc.C) {
    98  	w := s.assertWorker(c)
    99  	workertest.CleanKill(c, w)
   100  }
   101  
   102  func (s *CAASProvisionerSuite) assertOperatorCreated(c *gc.C, exists, updateCerts bool) {
   103  	s.provisionerFacade.life = "alive"
   104  	s.sendApplicationChanges(c, "myapp")
   105  
   106  	expectedCalls := 3
   107  	retryCallArgs := coretesting.LongRetryStrategy
   108  	retryCallArgs.Func = func() error {
   109  		nrCalls := len(s.caasClient.Calls())
   110  		if nrCalls >= expectedCalls {
   111  			return nil
   112  		}
   113  		if nrCalls > 0 {
   114  			s.caasClient.setOperatorExists(false)
   115  			s.caasClient.setTerminating(false)
   116  			s.clock.Advance(4 * time.Second)
   117  		}
   118  		return errors.Errorf("Not enough calls yet")
   119  	}
   120  	err := retry.Call(retryCallArgs)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  
   123  	callNames := []string{"OperatorExists", "Operator", "EnsureOperator"}
   124  	s.caasClient.CheckCallNames(c, callNames...)
   125  	c.Assert(s.caasClient.Calls(), gc.HasLen, expectedCalls)
   126  
   127  	args := s.caasClient.Calls()[0].Args
   128  	c.Assert(args, gc.HasLen, 1)
   129  	c.Assert(args[0], gc.Equals, "myapp")
   130  
   131  	ensureIndex := 2
   132  	args = s.caasClient.Calls()[ensureIndex].Args
   133  	c.Assert(args, gc.HasLen, 3)
   134  	c.Assert(args[0], gc.Equals, "myapp")
   135  	c.Assert(args[1], gc.Equals, "/var/lib/juju")
   136  	c.Assert(args[2], gc.FitsTypeOf, &caas.OperatorConfig{})
   137  	config := args[2].(*caas.OperatorConfig)
   138  	c.Assert(config.ImageDetails.RegistryPath, gc.Equals, "juju-operator-image")
   139  	c.Assert(config.Version, gc.Equals, version.MustParse("2.99.0"))
   140  	c.Assert(config.ResourceTags, jc.DeepEquals, map[string]string{"fred": "mary"})
   141  	if s.provisionerFacade.withStorage {
   142  		c.Assert(config.CharmStorage, jc.DeepEquals, &caas.CharmStorageParams{
   143  			Provider:     "kubernetes",
   144  			Size:         uint64(1024),
   145  			ResourceTags: map[string]string{"foo": "bar"},
   146  			Attributes:   map[string]interface{}{"key": "value"},
   147  		})
   148  	} else {
   149  		c.Assert(config.CharmStorage, gc.IsNil)
   150  	}
   151  	if updateCerts {
   152  		c.Assert(config.ConfigMapGeneration, gc.Equals, int64(1))
   153  	} else {
   154  		c.Assert(config.ConfigMapGeneration, gc.Equals, int64(0))
   155  	}
   156  
   157  	agentFile := filepath.Join(c.MkDir(), "agent.config")
   158  	err = os.WriteFile(agentFile, config.AgentConf, 0644)
   159  	c.Assert(err, jc.ErrorIsNil)
   160  	cfg, err := agent.ReadConfig(agentFile)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	c.Assert(cfg.CACert(), gc.Equals, coretesting.CACert)
   163  	addr, err := cfg.APIAddresses()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(addr, jc.DeepEquals, []string{"10.0.0.1:17070", "192.18.1.1:17070"})
   166  
   167  	operatorInfo, err := caas.UnmarshalOperatorInfo(config.OperatorInfo)
   168  	c.Assert(err, jc.ErrorIsNil)
   169  	c.Assert(operatorInfo.CACert, gc.Equals, coretesting.CACert)
   170  	c.Assert(operatorInfo.Cert, gc.Equals, coretesting.ServerCert)
   171  	c.Assert(operatorInfo.PrivateKey, gc.Equals, coretesting.ServerKey)
   172  
   173  	retryCallArgs.Func = func() error {
   174  		if len(s.provisionerFacade.stub.Calls()) > 0 {
   175  			return nil
   176  		}
   177  		return errors.Errorf("Not enough calls yet")
   178  	}
   179  	err = retry.Call(retryCallArgs)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  
   182  	if exists {
   183  		callNames := []string{"ApplicationCharmInfo", "Life", "OperatorProvisioningInfo"}
   184  		if updateCerts {
   185  			callNames = append(callNames, "IssueOperatorCertificate")
   186  		}
   187  		s.provisionerFacade.stub.CheckCallNames(c, callNames...)
   188  		c.Assert(s.provisionerFacade.stub.Calls()[0].Args[0], gc.Equals, "myapp")
   189  		c.Assert(s.provisionerFacade.stub.Calls()[1].Args[0], gc.Equals, "myapp")
   190  		return
   191  	}
   192  
   193  	s.provisionerFacade.stub.CheckCallNames(c, "ApplicationCharmInfo", "Life", "OperatorProvisioningInfo", "IssueOperatorCertificate", "SetPasswords")
   194  	c.Assert(s.provisionerFacade.stub.Calls()[0].Args[0], gc.Equals, "myapp")
   195  	passwords := s.provisionerFacade.stub.Calls()[4].Args[0].([]apicaasprovisioner.ApplicationPassword)
   196  
   197  	c.Assert(passwords, gc.HasLen, 1)
   198  	c.Assert(passwords[0].Name, gc.Equals, "myapp")
   199  	c.Assert(passwords[0].Password, gc.Not(gc.Equals), "")
   200  }
   201  
   202  func (s *CAASProvisionerSuite) TestNewApplicationCreatesNewOperator(c *gc.C) {
   203  	w := s.assertWorker(c)
   204  	defer workertest.CleanKill(c, w)
   205  
   206  	s.assertOperatorCreated(c, false, false)
   207  }
   208  
   209  func (s *CAASProvisionerSuite) TestNewApplicationNoStorage(c *gc.C) {
   210  	s.provisionerFacade.withStorage = false
   211  	w := s.assertWorker(c)
   212  	defer workertest.CleanKill(c, w)
   213  
   214  	s.assertOperatorCreated(c, false, false)
   215  }
   216  
   217  func (s *CAASProvisionerSuite) TestNewApplicationUpdatesOperator(c *gc.C) {
   218  	s.caasClient.operatorExists = true
   219  	s.caasClient.config = &caas.OperatorConfig{
   220  		ImageDetails: resources.DockerImageDetails{RegistryPath: "juju-operator-image"},
   221  		Version:      version.MustParse("2.99.0"),
   222  		AgentConf: []byte(fmt.Sprintf(`
   223  # format 2.0
   224  tag: application-myapp
   225  upgradedToVersion: 2.99.0
   226  controller: controller-deadbeef-1bad-500d-9000-4b1d0d06f00d
   227  model: model-deadbeef-0bad-400d-8000-4b1d0d06f00d
   228  oldpassword: wow
   229  cacert: %s
   230  apiaddresses:
   231  - 10.0.0.1:17070
   232  - 192.18.1.1:17070
   233  oldpassword: dxKwhgZPrNzXVTrZSxY1VLHA
   234  values: {}
   235  `[1:], strconv.Quote(coretesting.CACert))),
   236  		OperatorInfo: []byte(
   237  			fmt.Sprintf(
   238  				"private-key: %s\ncert: %s\nca-cert: %s\n",
   239  				strconv.Quote(coretesting.ServerKey),
   240  				strconv.Quote(coretesting.ServerCert),
   241  				strconv.Quote(coretesting.CACert),
   242  			),
   243  		),
   244  	}
   245  
   246  	w := s.assertWorker(c)
   247  	defer workertest.CleanKill(c, w)
   248  
   249  	s.assertOperatorCreated(c, true, false)
   250  }
   251  
   252  func (s *CAASProvisionerSuite) TestNewApplicationUpdatesOperatorAgentConfAPIAddresses(c *gc.C) {
   253  	s.caasClient.operatorExists = true
   254  	s.caasClient.config = &caas.OperatorConfig{
   255  		ImageDetails: resources.DockerImageDetails{RegistryPath: "juju-operator-image"},
   256  		Version:      version.MustParse("2.99.0"),
   257  		AgentConf: []byte(fmt.Sprintf(`
   258  # format 2.0
   259  tag: application-myapp
   260  upgradedToVersion: 2.99.0
   261  controller: controller-deadbeef-1bad-500d-9000-4b1d0d06f00d
   262  model: model-deadbeef-0bad-400d-8000-4b1d0d06f00d
   263  oldpassword: wow
   264  cacert: %s
   265  apiaddresses:
   266  - 8.8.8.6:17070 # this address will be updated to 10.0.0.1:17070
   267  - 192.18.1.1:17070
   268  oldpassword: dxKwhgZPrNzXVTrZSxY1VLHA
   269  values: {}
   270  mongoversion: "0.0"
   271  `[1:], strconv.Quote(coretesting.CACert))),
   272  		OperatorInfo: []byte(
   273  			fmt.Sprintf(
   274  				"private-key: %s\ncert: %s\nca-cert: %s\n",
   275  				strconv.Quote(coretesting.ServerKey),
   276  				strconv.Quote(coretesting.ServerCert),
   277  				strconv.Quote(coretesting.CACert),
   278  			),
   279  		),
   280  	}
   281  
   282  	w := s.assertWorker(c)
   283  	defer workertest.CleanKill(c, w)
   284  	s.assertOperatorCreated(c, true, false)
   285  }
   286  
   287  func (s *CAASProvisionerSuite) TestNewApplicationUpdatesOperatorAndIssueCerts(c *gc.C) {
   288  	s.caasClient.operatorExists = true
   289  	s.caasClient.config = &caas.OperatorConfig{
   290  		ImageDetails: resources.DockerImageDetails{RegistryPath: "juju-operator-image"},
   291  		Version:      version.MustParse("2.99.0"),
   292  		AgentConf: []byte(fmt.Sprintf(`
   293  # format 2.0
   294  tag: application-myapp
   295  upgradedToVersion: 2.99.0
   296  controller: controller-deadbeef-1bad-500d-9000-4b1d0d06f00d
   297  model: model-deadbeef-0bad-400d-8000-4b1d0d06f00d
   298  oldpassword: wow
   299  cacert: %s
   300  apiaddresses:
   301  - 10.0.0.1:17070
   302  - 192.18.1.1:17070
   303  oldpassword: dxKwhgZPrNzXVTrZSxY1VLHA
   304  values: {}
   305  `[1:], strconv.Quote(coretesting.CACert))),
   306  		ConfigMapGeneration: 1,
   307  	}
   308  
   309  	w := s.assertWorker(c)
   310  	defer workertest.CleanKill(c, w)
   311  
   312  	s.assertOperatorCreated(c, true, true)
   313  }
   314  
   315  func (s *CAASProvisionerSuite) TestNewApplicationWaitsOperatorTerminated(c *gc.C) {
   316  	s.caasClient.operatorExists = true
   317  	w := s.assertWorker(c)
   318  	defer workertest.CleanKill(c, w)
   319  
   320  	s.caasClient.setTerminating(true)
   321  	s.provisionerFacade.life = "alive"
   322  	s.sendApplicationChanges(c, "myapp")
   323  
   324  	lastLen := 0
   325  	gotOperatorCall := false
   326  
   327  	retryCallArgs := coretesting.LongRetryStrategy
   328  	retryCallArgs.Func = func() error {
   329  		calls := s.caasClient.Calls()
   330  		newCalls := calls[lastLen:]
   331  		lastLen = len(calls)
   332  		for _, call := range newCalls {
   333  			c.Logf("call to %s", call.FuncName)
   334  			switch call.FuncName {
   335  			case "OperatorExists":
   336  				s.caasClient.setOperatorExists(false)
   337  				s.caasClient.setTerminating(false)
   338  				s.clock.Advance(4 * time.Second)
   339  			case "Operator":
   340  				gotOperatorCall = true
   341  			case "EnsureOperator":
   342  				if !gotOperatorCall {
   343  					c.Errorf("missing call to Operator")
   344  				}
   345  				return nil
   346  			}
   347  		}
   348  		return errors.Errorf("missing expected calls")
   349  	}
   350  	err := retry.Call(retryCallArgs)
   351  	if err != nil {
   352  		c.Errorf("worker didn't wait for old operator to terminate")
   353  	}
   354  }
   355  
   356  func (s *CAASProvisionerSuite) TestApplicationDeletedRemovesOperator(c *gc.C) {
   357  	w := s.assertWorker(c)
   358  	defer workertest.CleanKill(c, w)
   359  
   360  	s.assertOperatorCreated(c, false, false)
   361  	s.caasClient.ResetCalls()
   362  	s.provisionerFacade.life = "dead"
   363  	s.sendApplicationChanges(c, "myapp")
   364  
   365  	retryCallArgs := coretesting.LongRetryStrategy
   366  	retryCallArgs.Func = func() error {
   367  		if len(s.caasClient.Calls()) > 0 {
   368  			return nil
   369  		}
   370  		return errors.Errorf("Not enough calls yet")
   371  	}
   372  	err := retry.Call(retryCallArgs)
   373  	c.Assert(err, jc.ErrorIsNil)
   374  	s.caasClient.CheckCallNames(c, "DeleteOperator")
   375  	c.Assert(s.caasClient.Calls()[0].Args[0], gc.Equals, "myapp")
   376  }
   377  
   378  func (s *CAASProvisionerSuite) TestV2CharmSkipsProcessing(c *gc.C) {
   379  	w := s.assertWorker(c)
   380  	defer workertest.CleanKill(c, w)
   381  
   382  	s.provisionerFacade.charmInfo.Manifest.Bases = []charm.Base{{}}
   383  	s.sendApplicationChanges(c, "app")
   384  
   385  	workertest.CheckAlive(c, w)
   386  }
   387  
   388  func (s *CAASProvisionerSuite) TestNotFoundCharmSkipsProcessing(c *gc.C) {
   389  	w := s.assertWorker(c)
   390  	defer workertest.CleanKill(c, w)
   391  
   392  	s.provisionerFacade.charmInfo = nil
   393  	s.sendApplicationChanges(c, "no-app")
   394  
   395  	workertest.CheckAlive(c, w)
   396  }
   397  
   398  func (s *CAASProvisionerSuite) sendApplicationChanges(c *gc.C, appNames ...string) {
   399  	select {
   400  	case s.provisionerFacade.applicationsWatcher.changes <- appNames:
   401  	case <-time.After(coretesting.LongWait):
   402  		c.Fatal("timed out sending applications change")
   403  	}
   404  }