github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/provisioner/container_initialisation_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/golang/mock/gomock"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	"github.com/pkg/errors"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/juju/worker.v1"
    18  	"gopkg.in/juju/worker.v1/workertest"
    19  
    20  	"github.com/juju/juju/agent"
    21  	apimocks "github.com/juju/juju/api/base/mocks"
    22  	"github.com/juju/juju/api/common"
    23  	apiprovisioner "github.com/juju/juju/api/provisioner"
    24  	provisionermocks "github.com/juju/juju/api/provisioner/mocks"
    25  	"github.com/juju/juju/apiserver/params"
    26  	"github.com/juju/juju/container"
    27  	"github.com/juju/juju/container/factory"
    28  	"github.com/juju/juju/container/testing"
    29  	"github.com/juju/juju/core/instance"
    30  	"github.com/juju/juju/core/machinelock"
    31  	"github.com/juju/juju/core/watcher"
    32  	coretesting "github.com/juju/juju/testing"
    33  	jujuversion "github.com/juju/juju/version"
    34  	jworker "github.com/juju/juju/worker"
    35  	"github.com/juju/juju/worker/mocks"
    36  	"github.com/juju/juju/worker/provisioner"
    37  )
    38  
    39  type containerSetupSuite struct {
    40  	coretesting.BaseSuite
    41  
    42  	modelUUID      utils.UUID
    43  	controllerUUID utils.UUID
    44  
    45  	initialiser  *testing.MockInitialiser
    46  	facadeCaller *apimocks.MockFacadeCaller
    47  	machine      *provisionermocks.MockMachineProvisioner
    48  	notifyWorker *mocks.MockWorker
    49  	manager      *testing.MockManager
    50  
    51  	machineLock *fakeMachineLock
    52  
    53  	// The done channel is used by tests to indicate that
    54  	// the worker has accomplished the scenario and can be stopped.
    55  	done chan struct{}
    56  }
    57  
    58  func (s *containerSetupSuite) SetUpTest(c *gc.C) {
    59  	s.BaseSuite.SetUpTest(c)
    60  
    61  	s.modelUUID = utils.MustNewUUID()
    62  	s.controllerUUID = utils.MustNewUUID()
    63  
    64  	s.machineLock = &fakeMachineLock{}
    65  	s.done = make(chan struct{})
    66  }
    67  
    68  var _ = gc.Suite(&containerSetupSuite{})
    69  
    70  func (s *containerSetupSuite) TestStartContainerStartsContainerProvisioner(c *gc.C) {
    71  	defer s.patch(c).Finish()
    72  
    73  	// Adding one new container machine.
    74  	s.notify([]string{"0/lxd/0"})
    75  
    76  	s.expectContainerManagerConfig("lxd")
    77  	s.initialiser.EXPECT().Initialise().Return(nil)
    78  
    79  	s.PatchValue(
    80  		&factory.NewContainerManager,
    81  		func(forType instance.ContainerType, conf container.ManagerConfig) (container.Manager, error) {
    82  			return s.manager, nil
    83  		})
    84  
    85  	_, runner := s.setUpContainerWorker(c)
    86  
    87  	// Watch the runner report. We are waiting for 2 workers to be started:
    88  	// the container watcher and the LXD provisioner.
    89  	workers := make(chan map[string]interface{})
    90  	go func() {
    91  		for {
    92  			rep := runner.Report()["workers"].(map[string]interface{})
    93  			if len(rep) == 2 {
    94  				workers <- rep
    95  				return
    96  			}
    97  			time.Sleep(time.Millisecond)
    98  		}
    99  	}()
   100  
   101  	// Check that the provisioner is there.
   102  	select {
   103  	case w := <-workers:
   104  		_, ok := w["lxd-provisioner"]
   105  		c.Check(ok, jc.IsTrue)
   106  	case <-time.After(coretesting.LongWait):
   107  		c.Errorf("timed out waiting for runner to start all workers")
   108  	}
   109  
   110  	s.cleanKill(c, runner)
   111  }
   112  
   113  func (s *containerSetupSuite) TestContainerManagerConfigError(c *gc.C) {
   114  	defer s.patch(c).Finish()
   115  
   116  	s.facadeCaller.EXPECT().FacadeCall(
   117  		"ContainerManagerConfig", params.ContainerManagerConfigParams{Type: "lxd"}, gomock.Any()).Return(
   118  		errors.New("boom"))
   119  
   120  	s.notify(nil)
   121  	handler, runner := s.setUpContainerWorker(c)
   122  	s.cleanKill(c, runner)
   123  
   124  	abort := make(chan struct{})
   125  	close(abort)
   126  	err := handler.Handle(abort, []string{"0/lxd/0"})
   127  	c.Assert(err, gc.ErrorMatches, ".*generating container manager config: boom")
   128  }
   129  
   130  func (s *containerSetupSuite) setUpContainerWorker(c *gc.C) (watcher.StringsHandler, *worker.Runner) {
   131  	runner := worker.NewRunner(worker.RunnerParams{
   132  		IsFatal:       func(_ error) bool { return true },
   133  		MoreImportant: func(_, _ error) bool { return false },
   134  		RestartDelay:  jworker.RestartDelay,
   135  	})
   136  
   137  	pState := apiprovisioner.NewStateFromFacade(s.facadeCaller)
   138  	watcherName := fmt.Sprintf("%s-container-watcher", s.machine.Id())
   139  
   140  	cfg, err := agent.NewAgentConfig(
   141  		agent.AgentConfigParams{
   142  			Paths:             agent.DefaultPaths,
   143  			Tag:               s.machine.MachineTag(),
   144  			UpgradedToVersion: jujuversion.Current,
   145  			Password:          "password",
   146  			Nonce:             "nonce",
   147  			APIAddresses:      []string{"0.0.0.0:12345"},
   148  			CACert:            coretesting.CACert,
   149  			Controller:        names.NewControllerTag(s.controllerUUID.String()),
   150  			Model:             names.NewModelTag(s.modelUUID.String()),
   151  		})
   152  	c.Assert(err, jc.ErrorIsNil)
   153  
   154  	args := provisioner.ContainerSetupParams{
   155  		Runner:              runner,
   156  		WorkerName:          watcherName,
   157  		SupportedContainers: instance.ContainerTypes,
   158  		Machine:             s.machine,
   159  		Provisioner:         pState,
   160  		Config:              cfg,
   161  		MachineLock:         s.machineLock,
   162  		CredentialAPI:       &credentialAPIForTest{},
   163  	}
   164  
   165  	// Stub out network config getter.
   166  	handler := provisioner.NewContainerSetupHandler(args)
   167  	handler.(*provisioner.ContainerSetup).SetGetNetConfig(
   168  		func(_ common.NetworkConfigSource) ([]params.NetworkConfig, error) {
   169  			return nil, nil
   170  		})
   171  
   172  	_ = runner.StartWorker(watcherName, func() (worker.Worker, error) {
   173  		return watcher.NewStringsWorker(watcher.StringsConfig{
   174  			Handler: handler,
   175  		})
   176  	})
   177  
   178  	return handler, runner
   179  }
   180  
   181  func (s *containerSetupSuite) patch(c *gc.C) *gomock.Controller {
   182  	ctrl := gomock.NewController(c)
   183  
   184  	s.initialiser = testing.NewMockInitialiser(ctrl)
   185  	s.facadeCaller = apimocks.NewMockFacadeCaller(ctrl)
   186  	s.notifyWorker = mocks.NewMockWorker(ctrl)
   187  	s.machine = provisionermocks.NewMockMachineProvisioner(ctrl)
   188  	s.manager = testing.NewMockManager(ctrl)
   189  
   190  	s.stubOutProvisioner(ctrl)
   191  
   192  	s.machine.EXPECT().Id().Return("0").AnyTimes()
   193  	s.machine.EXPECT().MachineTag().Return(names.NewMachineTag("0")).AnyTimes()
   194  
   195  	s.PatchValue(provisioner.GetContainerInitialiser, func(instance.ContainerType) container.Initialiser {
   196  		return s.initialiser
   197  	})
   198  
   199  	s.manager.EXPECT().ListContainers().Return(nil, nil).AnyTimes()
   200  
   201  	return ctrl
   202  }
   203  
   204  // stubOutProvisioner is used to effectively ignore provisioner calls that we
   205  // do not care about for testing container provisioning.
   206  // The bulk of the calls mocked here are called in
   207  // authentication.NewAPIAuthenticator, which is passed the provisioner's
   208  // client-side state by the provisioner worker.
   209  func (s *containerSetupSuite) stubOutProvisioner(ctrl *gomock.Controller) {
   210  	// We could have mocked only the base caller and not the FacadeCaller,
   211  	// but expectations would be verbose to the point of obfuscation.
   212  	// So we only mock the base caller for calls that use it directly,
   213  	// such as watcher acquisition.
   214  	caller := apimocks.NewMockAPICaller(ctrl)
   215  	cExp := caller.EXPECT()
   216  	cExp.BestFacadeVersion(gomock.Any()).Return(0).AnyTimes()
   217  	cExp.APICall("NotifyWatcher", 0, gomock.Any(), gomock.Any(), nil, gomock.Any()).Return(nil).AnyTimes()
   218  	cExp.APICall("StringsWatcher", 0, gomock.Any(), gomock.Any(), nil, gomock.Any()).Return(nil).AnyTimes()
   219  
   220  	fExp := s.facadeCaller.EXPECT()
   221  	fExp.RawAPICaller().Return(caller).AnyTimes()
   222  
   223  	notifySource := params.NotifyWatchResult{NotifyWatcherId: "who-cares"}
   224  	fExp.FacadeCall("WatchForModelConfigChanges", nil, gomock.Any()).SetArg(2, notifySource).Return(nil).AnyTimes()
   225  
   226  	modelCfgSource := params.ModelConfigResult{
   227  		Config: map[string]interface{}{
   228  			"uuid": s.modelUUID.String(),
   229  			"type": "maas",
   230  			"name": "container-init-test-model",
   231  		},
   232  	}
   233  	fExp.FacadeCall("ModelConfig", nil, gomock.Any()).SetArg(2, modelCfgSource).Return(nil).AnyTimes()
   234  
   235  	addrSource := params.StringsResult{Result: []string{"0.0.0.0"}}
   236  	fExp.FacadeCall("StateAddresses", nil, gomock.Any()).SetArg(2, addrSource).Return(nil).AnyTimes()
   237  	fExp.FacadeCall("APIAddresses", nil, gomock.Any()).SetArg(2, addrSource).Return(nil).AnyTimes()
   238  
   239  	certSource := params.BytesResult{Result: []byte(coretesting.CACert)}
   240  	fExp.FacadeCall("CACert", nil, gomock.Any()).SetArg(2, certSource).Return(nil).AnyTimes()
   241  
   242  	uuidSource := params.StringResult{Result: s.modelUUID.String()}
   243  	fExp.FacadeCall("ModelUUID", nil, gomock.Any()).SetArg(2, uuidSource).Return(nil).AnyTimes()
   244  
   245  	lifeSource := params.LifeResults{Results: []params.LifeResult{{Life: params.Alive}}}
   246  	fExp.FacadeCall("Life", gomock.Any(), gomock.Any()).SetArg(2, lifeSource).Return(nil).AnyTimes()
   247  
   248  	watchSource := params.StringsWatchResults{Results: []params.StringsWatchResult{{
   249  		StringsWatcherId: "whatever",
   250  		Changes:          []string{},
   251  	}}}
   252  	fExp.FacadeCall("WatchContainers", gomock.Any(), gomock.Any()).SetArg(2, watchSource).Return(nil).AnyTimes()
   253  	fExp.FacadeCall("WatchContainersCharmProfiles", gomock.Any(), gomock.Any()).SetArg(2, watchSource).Return(nil).AnyTimes()
   254  
   255  	watchOneSource := params.StringsWatchResult{
   256  		StringsWatcherId: "something",
   257  		Changes:          []string{},
   258  	}
   259  	fExp.FacadeCall("WatchModelMachinesCharmProfiles", gomock.Any(), gomock.Any()).SetArg(2, watchOneSource).Return(nil).AnyTimes()
   260  
   261  	controllerCfgSource := params.ControllerConfigResult{
   262  		Config: map[string]interface{}{"controller-uuid": s.controllerUUID.String()},
   263  	}
   264  	fExp.FacadeCall("ControllerConfig", nil, gomock.Any()).SetArg(2, controllerCfgSource).Return(nil).AnyTimes()
   265  }
   266  
   267  // notify returns a suite behaviour that will cause the upgrade-series watcher
   268  // to send a number of notifications equal to the supplied argument.
   269  // Once notifications have been consumed, we notify via the suite's channel.
   270  func (s *containerSetupSuite) notify(messages ...[]string) {
   271  	ch := make(chan []string)
   272  
   273  	go func() {
   274  		for _, m := range messages {
   275  			ch <- m
   276  		}
   277  		close(s.done)
   278  	}()
   279  
   280  	s.notifyWorker.EXPECT().Kill().AnyTimes()
   281  	s.notifyWorker.EXPECT().Wait().Return(nil).AnyTimes()
   282  
   283  	s.machine.EXPECT().WatchAllContainers().Return(
   284  		&fakeWatcher{
   285  			Worker: s.notifyWorker,
   286  			ch:     ch,
   287  		}, nil)
   288  }
   289  
   290  // expectContainerManagerConfig sets up expectations associated with
   291  // acquisition and decoration of container manager configuration.
   292  func (s *containerSetupSuite) expectContainerManagerConfig(cType instance.ContainerType) {
   293  	resultSource := params.ContainerManagerConfig{
   294  		ManagerConfig: map[string]string{"model-uuid": s.modelUUID.String()},
   295  	}
   296  	s.facadeCaller.EXPECT().FacadeCall(
   297  		"ContainerManagerConfig", params.ContainerManagerConfigParams{Type: cType}, gomock.Any(),
   298  	).SetArg(2, resultSource).MinTimes(1)
   299  
   300  	s.machine.EXPECT().AvailabilityZone().Return("az1", nil)
   301  }
   302  
   303  // cleanKill waits for notifications to be processed, then waits for the input
   304  // worker to be killed cleanly. If either ops time out, the test fails.
   305  func (s *containerSetupSuite) cleanKill(c *gc.C, w worker.Worker) {
   306  	select {
   307  	case <-s.done:
   308  	case <-time.After(coretesting.LongWait):
   309  		c.Errorf("timed out waiting for notifications to be consumed")
   310  	}
   311  	workertest.CleanKill(c, w)
   312  }
   313  
   314  type credentialAPIForTest struct{}
   315  
   316  func (*credentialAPIForTest) InvalidateModelCredential(reason string) error {
   317  	return nil
   318  }
   319  
   320  type fakeMachineLock struct {
   321  	mu sync.Mutex
   322  }
   323  
   324  func (f *fakeMachineLock) Acquire(spec machinelock.Spec) (func(), error) {
   325  	f.mu.Lock()
   326  	return func() {
   327  		f.mu.Unlock()
   328  	}, nil
   329  }
   330  
   331  func (f *fakeMachineLock) Report(opts ...machinelock.ReportOption) (string, error) {
   332  	return "", nil
   333  }
   334  
   335  type fakeWatcher struct {
   336  	worker.Worker
   337  	ch <-chan []string
   338  }
   339  
   340  func (w *fakeWatcher) Changes() watcher.StringsChannel {
   341  	return w.ch
   342  }