github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/environ_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 "github.com/golang/mock/gomock" 8 "github.com/juju/cmd/cmdtesting" 9 "github.com/juju/errors" 10 gitjujutesting "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 "github.com/lxc/lxd/shared/api" 13 gc "gopkg.in/check.v1" 14 "gopkg.in/juju/charm.v6" 15 16 "github.com/juju/juju/cloudconfig/instancecfg" 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/juju/core/instance" 19 "github.com/juju/juju/core/lxdprofile" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/environs/context" 22 envtesting "github.com/juju/juju/environs/testing" 23 "github.com/juju/juju/provider/lxd" 24 coretesting "github.com/juju/juju/testing" 25 ) 26 27 var errTestUnAuth = errors.New("not authorized") 28 29 type environSuite struct { 30 lxd.BaseSuite 31 32 callCtx context.ProviderCallContext 33 invalidCredential bool 34 } 35 36 var _ = gc.Suite(&environSuite{}) 37 38 func (s *environSuite) SetUpTest(c *gc.C) { 39 s.BaseSuite.SetUpTest(c) 40 s.callCtx = &context.CloudCallContext{ 41 InvalidateCredentialFunc: func(string) error { 42 s.invalidCredential = true 43 return nil 44 }, 45 } 46 } 47 48 func (s *environSuite) TearDownTest(c *gc.C) { 49 s.invalidCredential = false 50 s.BaseSuite.TearDownTest(c) 51 } 52 53 func (s *environSuite) TestName(c *gc.C) { 54 c.Check(s.Env.Name(), gc.Equals, "lxd") 55 } 56 57 func (s *environSuite) TestProvider(c *gc.C) { 58 c.Assert(s.Env.Provider(), gc.Equals, s.Provider) 59 } 60 61 func (s *environSuite) TestSetConfigOkay(c *gc.C) { 62 err := s.Env.SetConfig(s.Config) 63 c.Assert(err, jc.ErrorIsNil) 64 65 c.Check(lxd.ExposeEnvConfig(s.Env), jc.DeepEquals, s.EnvConfig) 66 // Ensure the client did not change. 67 c.Check(lxd.ExposeEnvServer(s.Env), gc.Equals, s.Client) 68 } 69 70 func (s *environSuite) TestSetConfigNoAPI(c *gc.C) { 71 err := s.Env.SetConfig(s.Config) 72 73 c.Assert(err, jc.ErrorIsNil) 74 } 75 76 func (s *environSuite) TestConfig(c *gc.C) { 77 cfg := s.Env.Config() 78 79 c.Check(cfg, jc.DeepEquals, s.Config) 80 } 81 82 func (s *environSuite) TestBootstrapOkay(c *gc.C) { 83 s.Common.BootstrapResult = &environs.BootstrapResult{ 84 Arch: "amd64", 85 Series: "trusty", 86 CloudBootstrapFinalizer: func(environs.BootstrapContext, *instancecfg.InstanceConfig, environs.BootstrapDialOpts) error { 87 return nil 88 }, 89 } 90 91 ctx := cmdtesting.Context(c) 92 params := environs.BootstrapParams{ 93 ControllerConfig: coretesting.FakeControllerConfig(), 94 } 95 result, err := s.Env.Bootstrap(modelcmd.BootstrapContext(ctx), s.callCtx, params) 96 c.Assert(err, jc.ErrorIsNil) 97 98 c.Check(result.Arch, gc.Equals, "amd64") 99 c.Check(result.Series, gc.Equals, "trusty") 100 // We don't check bsFinalizer because functions cannot be compared. 101 c.Check(result.CloudBootstrapFinalizer, gc.NotNil) 102 103 out := cmdtesting.Stderr(ctx) 104 c.Assert(out, gc.Equals, "To configure your system to better support LXD containers, please see: https://github.com/lxc/lxd/blob/master/doc/production-setup.md\n") 105 } 106 107 func (s *environSuite) TestBootstrapAPI(c *gc.C) { 108 ctx := envtesting.BootstrapContext(c) 109 params := environs.BootstrapParams{ 110 ControllerConfig: coretesting.FakeControllerConfig(), 111 } 112 _, err := s.Env.Bootstrap(ctx, s.callCtx, params) 113 c.Assert(err, jc.ErrorIsNil) 114 115 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{{ 116 FuncName: "Bootstrap", 117 Args: []interface{}{ 118 ctx, 119 s.callCtx, 120 params, 121 }, 122 }}) 123 } 124 125 func (s *environSuite) TestDestroy(c *gc.C) { 126 s.Client.Volumes = map[string][]api.StorageVolume{ 127 "juju": {{ 128 Name: "not-ours", 129 StorageVolumePut: api.StorageVolumePut{ 130 Config: map[string]string{ 131 "user.juju-model-uuid": "other", 132 }, 133 }, 134 }, { 135 Name: "ours", 136 StorageVolumePut: api.StorageVolumePut{ 137 Config: map[string]string{ 138 "user.juju-model-uuid": s.Config.UUID(), 139 }, 140 }, 141 }}, 142 } 143 144 err := s.Env.Destroy(s.callCtx) 145 c.Assert(err, jc.ErrorIsNil) 146 147 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 148 {"Destroy", []interface{}{s.callCtx}}, 149 {"StorageSupported", nil}, 150 {"GetStoragePools", nil}, 151 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 152 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 153 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 154 }) 155 } 156 157 func (s *environSuite) TestDestroyInvalidCredentials(c *gc.C) { 158 c.Assert(s.invalidCredential, jc.IsFalse) 159 s.Client.Stub.SetErrors(errTestUnAuth) 160 err := s.Env.Destroy(s.callCtx) 161 c.Assert(err, gc.ErrorMatches, "not authorized") 162 c.Assert(s.invalidCredential, jc.IsTrue) 163 } 164 165 func (s *environSuite) TestDestroyInvalidCredentialsDestroyingFileSystems(c *gc.C) { 166 c.Assert(s.invalidCredential, jc.IsFalse) 167 // DeleteStoragePoolVolume will error w/ un-auth. 168 s.Client.Stub.SetErrors(nil, nil, nil, errTestUnAuth) 169 170 s.Client.Volumes = map[string][]api.StorageVolume{ 171 "juju": {{ 172 Name: "ours", 173 StorageVolumePut: api.StorageVolumePut{ 174 Config: map[string]string{ 175 "user.juju-model-uuid": s.Config.UUID(), 176 }, 177 }, 178 }}, 179 } 180 err := s.Env.Destroy(s.callCtx) 181 c.Assert(err, gc.ErrorMatches, ".* not authorized") 182 c.Assert(s.invalidCredential, jc.IsTrue) 183 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 184 {"Destroy", []interface{}{s.callCtx}}, 185 {"StorageSupported", nil}, 186 {"GetStoragePools", nil}, 187 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 188 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 189 }) 190 } 191 192 func (s *environSuite) TestDestroyController(c *gc.C) { 193 s.UpdateConfig(c, map[string]interface{}{ 194 "controller-uuid": s.Config.UUID(), 195 }) 196 s.Stub.ResetCalls() 197 198 s.Client.Volumes = map[string][]api.StorageVolume{ 199 "juju": {{ 200 Name: "not-ours", 201 StorageVolumePut: api.StorageVolumePut{ 202 Config: map[string]string{ 203 "user.juju-controller-uuid": "other", 204 }, 205 }, 206 }, { 207 Name: "ours", 208 StorageVolumePut: api.StorageVolumePut{ 209 Config: map[string]string{ 210 "user.juju-controller-uuid": s.Config.UUID(), 211 }, 212 }, 213 }}, 214 } 215 216 // machine0 is in the controller model. 217 machine0 := s.NewContainer(c, "juju-controller-machine-0") 218 machine0.Config["user.juju-model-uuid"] = s.Config.UUID() 219 machine0.Config["user.juju-controller-uuid"] = s.Config.UUID() 220 221 // machine1 is not in the controller model, but managed 222 // by the same controller. 223 machine1 := s.NewContainer(c, "juju-hosted-machine-1") 224 machine1.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID() 225 machine1.Config["user.juju-controller-uuid"] = s.Config.UUID() 226 227 // machine2 is not managed by the same controller. 228 machine2 := s.NewContainer(c, "juju-controller-machine-2") 229 machine2.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID() 230 machine2.Config["user.juju-controller-uuid"] = "not-" + s.Config.UUID() 231 232 s.Client.Containers = append(s.Client.Containers, *machine0, *machine1, *machine2) 233 234 err := s.Env.DestroyController(s.callCtx, s.Config.UUID()) 235 c.Assert(err, jc.ErrorIsNil) 236 237 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 238 {"Destroy", []interface{}{s.callCtx}}, 239 {"StorageSupported", nil}, 240 {"GetStoragePools", nil}, 241 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 242 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 243 {"AliveContainers", []interface{}{"juju-"}}, 244 {"RemoveContainers", []interface{}{[]string{machine1.Name}}}, 245 {"StorageSupported", nil}, 246 {"GetStoragePools", nil}, 247 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 248 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 249 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 250 }) 251 } 252 253 func (s *environSuite) TestDestroyControllerInvalidCredentialsHostedModels(c *gc.C) { 254 c.Assert(s.invalidCredential, jc.IsFalse) 255 s.UpdateConfig(c, map[string]interface{}{ 256 "controller-uuid": s.Config.UUID(), 257 }) 258 s.Stub.ResetCalls() 259 260 s.Client.Volumes = map[string][]api.StorageVolume{ 261 "juju": {{ 262 Name: "ours", 263 StorageVolumePut: api.StorageVolumePut{ 264 Config: map[string]string{ 265 "user.juju-controller-uuid": s.Config.UUID(), 266 }, 267 }, 268 }}, 269 } 270 271 // machine0 is in the controller model. 272 machine0 := s.NewContainer(c, "juju-controller-machine-0") 273 machine0.Config["user.juju-model-uuid"] = s.Config.UUID() 274 machine0.Config["user.juju-controller-uuid"] = s.Config.UUID() 275 276 s.Client.Containers = append(s.Client.Containers, *machine0) 277 278 // RemoveContainers will error not-auth. 279 s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, errTestUnAuth) 280 281 err := s.Env.DestroyController(s.callCtx, s.Config.UUID()) 282 c.Assert(err, gc.ErrorMatches, "not authorized") 283 c.Assert(s.invalidCredential, jc.IsTrue) 284 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 285 {"Destroy", []interface{}{s.callCtx}}, 286 {"StorageSupported", nil}, 287 {"GetStoragePools", nil}, 288 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 289 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 290 {"AliveContainers", []interface{}{"juju-"}}, 291 {"RemoveContainers", []interface{}{[]string{}}}, 292 }) 293 s.Stub.CheckCallNames(c, 294 "Destroy", 295 "StorageSupported", 296 "GetStoragePools", 297 "GetStoragePoolVolumes", 298 "GetStoragePoolVolumes", 299 "AliveContainers", 300 "RemoveContainers") 301 } 302 303 func (s *environSuite) TestDestroyControllerInvalidCredentialsDestroyFilesystem(c *gc.C) { 304 c.Assert(s.invalidCredential, jc.IsFalse) 305 s.UpdateConfig(c, map[string]interface{}{ 306 "controller-uuid": s.Config.UUID(), 307 }) 308 s.Stub.ResetCalls() 309 310 s.Client.Volumes = map[string][]api.StorageVolume{ 311 "juju": {{ 312 Name: "ours", 313 StorageVolumePut: api.StorageVolumePut{ 314 Config: map[string]string{ 315 "user.juju-controller-uuid": s.Config.UUID(), 316 }, 317 }, 318 }}, 319 } 320 321 // machine0 is in the controller model. 322 machine0 := s.NewContainer(c, "juju-controller-machine-0") 323 machine0.Config["user.juju-model-uuid"] = s.Config.UUID() 324 machine0.Config["user.juju-controller-uuid"] = s.Config.UUID() 325 326 s.Client.Containers = append(s.Client.Containers, *machine0) 327 328 // RemoveContainers will error not-auth. 329 s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, nil, nil, nil, errTestUnAuth) 330 331 err := s.Env.DestroyController(s.callCtx, s.Config.UUID()) 332 c.Assert(err, gc.ErrorMatches, ".*not authorized") 333 c.Assert(s.invalidCredential, jc.IsTrue) 334 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 335 {"Destroy", []interface{}{s.callCtx}}, 336 {"StorageSupported", nil}, 337 {"GetStoragePools", nil}, 338 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 339 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 340 {"AliveContainers", []interface{}{"juju-"}}, 341 {"RemoveContainers", []interface{}{[]string{}}}, 342 {"StorageSupported", nil}, 343 {"GetStoragePools", nil}, 344 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 345 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 346 }) 347 } 348 349 func (s *environSuite) TestAvailabilityZonesInvalidCredentials(c *gc.C) { 350 c.Assert(s.invalidCredential, jc.IsFalse) 351 // GetClusterMembers will return un-auth error 352 s.Client.Stub.SetErrors(errTestUnAuth) 353 _, err := s.Env.AvailabilityZones(s.callCtx) 354 c.Assert(err, gc.ErrorMatches, ".*not authorized") 355 c.Assert(s.invalidCredential, jc.IsTrue) 356 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 357 {"IsClustered", nil}, 358 {"GetClusterMembers", nil}, 359 }) 360 } 361 362 func (s *environSuite) TestInstanceAvailabilityZoneNamesInvalidCredentials(c *gc.C) { 363 c.Assert(s.invalidCredential, jc.IsFalse) 364 // AliveContainers will return un-auth error 365 s.Client.Stub.SetErrors(errTestUnAuth) 366 367 // the call to Instances takes care of updating invalid credential details 368 _, err := s.Env.InstanceAvailabilityZoneNames(s.callCtx, []instance.Id{"not-valid"}) 369 c.Assert(err, gc.ErrorMatches, ".*not authorized") 370 c.Assert(s.invalidCredential, jc.IsTrue) 371 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 372 {"AliveContainers", []interface{}{s.Prefix()}}, 373 }) 374 } 375 376 type environProfileSuite struct { 377 lxd.EnvironSuite 378 379 callCtx context.ProviderCallContext 380 } 381 382 var _ = gc.Suite(&environProfileSuite{}) 383 384 func (s *environProfileSuite) TestMaybeWriteLXDProfile(c *gc.C) { 385 ctrl := gomock.NewController(c) 386 defer ctrl.Finish() 387 388 svr := lxd.NewMockServer(ctrl) 389 exp := svr.EXPECT() 390 gomock.InOrder( 391 exp.HasProfile("testname").Return(true, nil), 392 exp.HasProfile("testname").Return(false, nil), 393 exp.CreateProfile(api.ProfilesPost{ 394 Name: "testname", 395 ProfilePut: api.ProfilePut{ 396 Config: map[string]string{ 397 "security.nesting": "true", 398 }, 399 Description: "test profile", 400 }, 401 }).Return(nil), 402 ) 403 404 env := s.NewEnviron(c, svr, nil) 405 lxdEnv, ok := env.(environs.LXDProfiler) 406 c.Assert(ok, jc.IsTrue) 407 err := lxdEnv.MaybeWriteLXDProfile("testname", nil) 408 c.Assert(err, jc.ErrorIsNil) 409 err = lxdEnv.MaybeWriteLXDProfile("testname", &charm.LXDProfile{ 410 Config: map[string]string{ 411 "security.nesting": "true", 412 }, 413 Description: "test profile", 414 }) 415 c.Assert(err, jc.ErrorIsNil) 416 } 417 418 func (s *environProfileSuite) TestLXDProfileNames(c *gc.C) { 419 ctrl := gomock.NewController(c) 420 defer ctrl.Finish() 421 422 svr := lxd.NewMockServer(ctrl) 423 exp := svr.EXPECT() 424 425 exp.GetContainerProfiles("testname").Return([]string{ 426 lxdprofile.Name("foo", "bar", 1), 427 }, nil) 428 429 env := s.NewEnviron(c, svr, nil) 430 lxdEnv, ok := env.(environs.LXDProfiler) 431 c.Assert(ok, jc.IsTrue) 432 result, err := lxdEnv.LXDProfileNames("testname") 433 c.Assert(err, jc.ErrorIsNil) 434 c.Assert(result, jc.DeepEquals, []string{ 435 lxdprofile.Name("foo", "bar", 1), 436 }) 437 } 438 439 func (s *environProfileSuite) TestReplaceOrAddInstanceProfile(c *gc.C) { 440 ctrl := gomock.NewController(c) 441 defer ctrl.Finish() 442 443 instId := "testme" 444 old := "old-profile" 445 new := "new-profile" 446 447 svr := lxd.NewMockServer(ctrl) 448 exp := svr.EXPECT() 449 gomock.InOrder( 450 exp.HasProfile(new).Return(false, nil), 451 exp.CreateProfile(api.ProfilesPost{ 452 Name: new, 453 ProfilePut: api.ProfilePut{ 454 Config: map[string]string{ 455 "security.nesting": "true", 456 }, 457 Description: "test profile", 458 }, 459 }).Return(nil), 460 exp.ReplaceOrAddContainerProfile(instId, old, new).Return(nil), 461 exp.DeleteProfile(old), 462 exp.GetContainerProfiles(instId).Return([]string{"default", "juju-default", new}, nil), 463 ) 464 465 env := s.NewEnviron(c, svr, nil) 466 lxdEnv, ok := env.(environs.LXDProfiler) 467 c.Assert(ok, jc.IsTrue) 468 put := &charm.LXDProfile{ 469 Config: map[string]string{ 470 "security.nesting": "true", 471 }, 472 Description: "test profile", 473 } 474 obtained, err := lxdEnv.ReplaceOrAddInstanceProfile(instId, old, new, put) 475 c.Assert(err, jc.ErrorIsNil) 476 c.Assert(obtained, gc.DeepEquals, []string{"default", "juju-default", new}) 477 }