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 }