github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "context" 8 stdcontext "context" 9 10 "github.com/canonical/lxd/shared/api" 11 "github.com/juju/cmd/v3/cmdtesting" 12 "github.com/juju/errors" 13 gitjujutesting "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "go.uber.org/mock/gomock" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/cloudconfig/instancecfg" 19 "github.com/juju/juju/cmd/modelcmd" 20 corebase "github.com/juju/juju/core/base" 21 "github.com/juju/juju/core/instance" 22 "github.com/juju/juju/core/lxdprofile" 23 "github.com/juju/juju/environs" 24 environscloudspec "github.com/juju/juju/environs/cloudspec" 25 envcontext "github.com/juju/juju/environs/context" 26 envtesting "github.com/juju/juju/environs/testing" 27 "github.com/juju/juju/provider/lxd" 28 coretesting "github.com/juju/juju/testing" 29 ) 30 31 var errTestUnAuth = errors.New("not authorized") 32 33 type environSuite struct { 34 lxd.BaseSuite 35 36 callCtx envcontext.ProviderCallContext 37 invalidCredential bool 38 } 39 40 var _ = gc.Suite(&environSuite{}) 41 42 func (s *environSuite) SetUpTest(c *gc.C) { 43 s.BaseSuite.SetUpTest(c) 44 s.callCtx = &envcontext.CloudCallContext{ 45 InvalidateCredentialFunc: func(string) error { 46 s.invalidCredential = true 47 return nil 48 }, 49 } 50 } 51 52 func (s *environSuite) TearDownTest(c *gc.C) { 53 s.invalidCredential = false 54 s.BaseSuite.TearDownTest(c) 55 } 56 57 func (s *environSuite) TestName(c *gc.C) { 58 c.Check(s.Env.Name(), gc.Equals, "lxd") 59 } 60 61 func (s *environSuite) TestProvider(c *gc.C) { 62 c.Assert(s.Env.Provider(), gc.Equals, s.Provider) 63 } 64 65 func (s *environSuite) TestSetConfigOkay(c *gc.C) { 66 err := s.Env.SetConfig(s.Config) 67 c.Assert(err, jc.ErrorIsNil) 68 69 c.Check(lxd.ExposeEnvConfig(s.Env), jc.DeepEquals, s.EnvConfig) 70 // Ensure the client did not change. 71 c.Check(lxd.ExposeEnvServer(s.Env), gc.Equals, s.Client) 72 } 73 74 func (s *environSuite) TestSetConfigNoAPI(c *gc.C) { 75 err := s.Env.SetConfig(s.Config) 76 77 c.Assert(err, jc.ErrorIsNil) 78 } 79 80 func (s *environSuite) TestConfig(c *gc.C) { 81 cfg := s.Env.Config() 82 83 c.Check(cfg, jc.DeepEquals, s.Config) 84 } 85 86 func (s *environSuite) TestBootstrapOkay(c *gc.C) { 87 s.Common.BootstrapResult = &environs.BootstrapResult{ 88 Arch: "amd64", 89 Base: corebase.MakeDefaultBase("ubuntu", "22.04"), 90 CloudBootstrapFinalizer: func(environs.BootstrapContext, *instancecfg.InstanceConfig, environs.BootstrapDialOpts) error { 91 return nil 92 }, 93 } 94 95 ctx := cmdtesting.Context(c) 96 params := environs.BootstrapParams{ 97 ControllerConfig: coretesting.FakeControllerConfig(), 98 SupportedBootstrapSeries: coretesting.FakeSupportedJujuSeries, 99 } 100 result, err := s.Env.Bootstrap(modelcmd.BootstrapContext(context.Background(), ctx), s.callCtx, params) 101 c.Assert(err, jc.ErrorIsNil) 102 103 c.Check(result.Arch, gc.Equals, "amd64") 104 c.Check(result.Base.DisplayString(), gc.Equals, "ubuntu@22.04") 105 // We don't check bsFinalizer because functions cannot be compared. 106 c.Check(result.CloudBootstrapFinalizer, gc.NotNil) 107 108 out := cmdtesting.Stderr(ctx) 109 c.Assert(out, gc.Matches, "To configure your system to better support LXD containers, please see: .*\n") 110 } 111 112 func (s *environSuite) TestBootstrapAPI(c *gc.C) { 113 ctx := envtesting.BootstrapContext(context.TODO(), c) 114 params := environs.BootstrapParams{ 115 ControllerConfig: coretesting.FakeControllerConfig(), 116 SupportedBootstrapSeries: coretesting.FakeSupportedJujuSeries, 117 } 118 _, err := s.Env.Bootstrap(ctx, s.callCtx, params) 119 c.Assert(err, jc.ErrorIsNil) 120 121 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{{ 122 FuncName: "Bootstrap", 123 Args: []interface{}{ 124 ctx, 125 s.callCtx, 126 params, 127 }, 128 }}) 129 } 130 131 func (s *environSuite) TestDestroy(c *gc.C) { 132 s.Client.Volumes = map[string][]api.StorageVolume{ 133 "juju": {{ 134 Name: "not-ours", 135 StorageVolumePut: api.StorageVolumePut{ 136 Config: map[string]string{ 137 "user.juju-model-uuid": "other", 138 }, 139 }, 140 }, { 141 Name: "ours", 142 StorageVolumePut: api.StorageVolumePut{ 143 Config: map[string]string{ 144 "user.juju-model-uuid": s.Config.UUID(), 145 }, 146 }, 147 }}, 148 } 149 150 err := s.Env.Destroy(s.callCtx) 151 c.Assert(err, jc.ErrorIsNil) 152 153 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 154 {"Destroy", []interface{}{s.callCtx}}, 155 {"StorageSupported", nil}, 156 {"GetStoragePools", nil}, 157 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 158 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 159 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 160 }) 161 } 162 163 func (s *environSuite) TestDestroyInvalidCredentials(c *gc.C) { 164 c.Assert(s.invalidCredential, jc.IsFalse) 165 s.Client.Stub.SetErrors(errTestUnAuth) 166 err := s.Env.Destroy(s.callCtx) 167 c.Assert(err, gc.ErrorMatches, "not authorized") 168 c.Assert(s.invalidCredential, jc.IsTrue) 169 } 170 171 func (s *environSuite) TestDestroyInvalidCredentialsDestroyingFileSystems(c *gc.C) { 172 c.Assert(s.invalidCredential, jc.IsFalse) 173 // DeleteStoragePoolVolume will error w/ un-auth. 174 s.Client.Stub.SetErrors(nil, nil, nil, errTestUnAuth) 175 176 s.Client.Volumes = map[string][]api.StorageVolume{ 177 "juju": {{ 178 Name: "ours", 179 StorageVolumePut: api.StorageVolumePut{ 180 Config: map[string]string{ 181 "user.juju-model-uuid": s.Config.UUID(), 182 }, 183 }, 184 }}, 185 } 186 err := s.Env.Destroy(s.callCtx) 187 c.Assert(err, gc.ErrorMatches, ".* not authorized") 188 c.Assert(s.invalidCredential, jc.IsTrue) 189 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 190 {"Destroy", []interface{}{s.callCtx}}, 191 {"StorageSupported", nil}, 192 {"GetStoragePools", nil}, 193 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 194 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 195 }) 196 } 197 198 func (s *environSuite) TestDestroyController(c *gc.C) { 199 s.UpdateConfig(c, map[string]interface{}{ 200 "controller-uuid": s.Config.UUID(), 201 }) 202 s.Stub.ResetCalls() 203 204 s.Client.Volumes = map[string][]api.StorageVolume{ 205 "juju": {{ 206 Name: "not-ours", 207 StorageVolumePut: api.StorageVolumePut{ 208 Config: map[string]string{ 209 "user.juju-controller-uuid": "other", 210 }, 211 }, 212 }, { 213 Name: "ours", 214 StorageVolumePut: api.StorageVolumePut{ 215 Config: map[string]string{ 216 "user.juju-controller-uuid": s.Config.UUID(), 217 }, 218 }, 219 }}, 220 } 221 222 // machine0 is in the controller model. 223 machine0 := s.NewContainer(c, "juju-controller-machine-0") 224 machine0.Config["user.juju-model-uuid"] = s.Config.UUID() 225 machine0.Config["user.juju-controller-uuid"] = s.Config.UUID() 226 227 // machine1 is not in the controller model, but managed 228 // by the same controller. 229 machine1 := s.NewContainer(c, "juju-hosted-machine-1") 230 machine1.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID() 231 machine1.Config["user.juju-controller-uuid"] = s.Config.UUID() 232 233 // machine2 is not managed by the same controller. 234 machine2 := s.NewContainer(c, "juju-controller-machine-2") 235 machine2.Config["user.juju-model-uuid"] = "not-" + s.Config.UUID() 236 machine2.Config["user.juju-controller-uuid"] = "not-" + s.Config.UUID() 237 238 s.Client.Containers = append(s.Client.Containers, *machine0, *machine1, *machine2) 239 240 err := s.Env.DestroyController(s.callCtx, s.Config.UUID()) 241 c.Assert(err, jc.ErrorIsNil) 242 243 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 244 {"Destroy", []interface{}{s.callCtx}}, 245 {"StorageSupported", nil}, 246 {"GetStoragePools", nil}, 247 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 248 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 249 {"AliveContainers", []interface{}{"juju-"}}, 250 {"RemoveContainers", []interface{}{[]string{machine1.Name}}}, 251 {"StorageSupported", nil}, 252 {"GetStoragePools", nil}, 253 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 254 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 255 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 256 }) 257 } 258 259 func (s *environSuite) TestDestroyControllerInvalidCredentialsHostedModels(c *gc.C) { 260 c.Assert(s.invalidCredential, jc.IsFalse) 261 s.UpdateConfig(c, map[string]interface{}{ 262 "controller-uuid": s.Config.UUID(), 263 }) 264 s.Stub.ResetCalls() 265 266 s.Client.Volumes = map[string][]api.StorageVolume{ 267 "juju": {{ 268 Name: "ours", 269 StorageVolumePut: api.StorageVolumePut{ 270 Config: map[string]string{ 271 "user.juju-controller-uuid": s.Config.UUID(), 272 }, 273 }, 274 }}, 275 } 276 277 // machine0 is in the controller model. 278 machine0 := s.NewContainer(c, "juju-controller-machine-0") 279 machine0.Config["user.juju-model-uuid"] = s.Config.UUID() 280 machine0.Config["user.juju-controller-uuid"] = s.Config.UUID() 281 282 s.Client.Containers = append(s.Client.Containers, *machine0) 283 284 // RemoveContainers will error not-auth. 285 s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, errTestUnAuth) 286 287 err := s.Env.DestroyController(s.callCtx, s.Config.UUID()) 288 c.Assert(err, gc.ErrorMatches, "not authorized") 289 c.Assert(s.invalidCredential, jc.IsTrue) 290 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 291 {"Destroy", []interface{}{s.callCtx}}, 292 {"StorageSupported", nil}, 293 {"GetStoragePools", nil}, 294 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 295 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 296 {"AliveContainers", []interface{}{"juju-"}}, 297 {"RemoveContainers", []interface{}{[]string{}}}, 298 }) 299 s.Stub.CheckCallNames(c, 300 "Destroy", 301 "StorageSupported", 302 "GetStoragePools", 303 "GetStoragePoolVolumes", 304 "GetStoragePoolVolumes", 305 "AliveContainers", 306 "RemoveContainers") 307 } 308 309 func (s *environSuite) TestDestroyControllerInvalidCredentialsDestroyFilesystem(c *gc.C) { 310 c.Assert(s.invalidCredential, jc.IsFalse) 311 s.UpdateConfig(c, map[string]interface{}{ 312 "controller-uuid": s.Config.UUID(), 313 }) 314 s.Stub.ResetCalls() 315 316 s.Client.Volumes = map[string][]api.StorageVolume{ 317 "juju": {{ 318 Name: "ours", 319 StorageVolumePut: api.StorageVolumePut{ 320 Config: map[string]string{ 321 "user.juju-controller-uuid": s.Config.UUID(), 322 }, 323 }, 324 }}, 325 } 326 327 // machine0 is in the controller model. 328 machine0 := s.NewContainer(c, "juju-controller-machine-0") 329 machine0.Config["user.juju-model-uuid"] = s.Config.UUID() 330 machine0.Config["user.juju-controller-uuid"] = s.Config.UUID() 331 332 s.Client.Containers = append(s.Client.Containers, *machine0) 333 334 // RemoveContainers will error not-auth. 335 s.Client.Stub.SetErrors(nil, nil, nil, nil, nil, nil, nil, nil, errTestUnAuth) 336 337 err := s.Env.DestroyController(s.callCtx, s.Config.UUID()) 338 c.Assert(err, gc.ErrorMatches, ".*not authorized") 339 c.Assert(s.invalidCredential, jc.IsTrue) 340 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 341 {"Destroy", []interface{}{s.callCtx}}, 342 {"StorageSupported", nil}, 343 {"GetStoragePools", nil}, 344 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 345 {"GetStoragePoolVolumes", []interface{}{"juju-zfs"}}, 346 {"AliveContainers", []interface{}{"juju-"}}, 347 {"RemoveContainers", []interface{}{[]string{}}}, 348 {"StorageSupported", nil}, 349 {"GetStoragePools", nil}, 350 {"GetStoragePoolVolumes", []interface{}{"juju"}}, 351 {"DeleteStoragePoolVolume", []interface{}{"juju", "custom", "ours"}}, 352 }) 353 } 354 355 func (s *environSuite) TestAvailabilityZonesInvalidCredentials(c *gc.C) { 356 c.Assert(s.invalidCredential, jc.IsFalse) 357 // GetClusterMembers will return un-auth error 358 s.Client.Stub.SetErrors(errTestUnAuth) 359 _, err := s.Env.AvailabilityZones(s.callCtx) 360 c.Assert(err, gc.ErrorMatches, ".*not authorized") 361 c.Assert(s.invalidCredential, jc.IsTrue) 362 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 363 {"IsClustered", nil}, 364 {"GetClusterMembers", nil}, 365 }) 366 } 367 368 func (s *environSuite) TestInstanceAvailabilityZoneNamesInvalidCredentials(c *gc.C) { 369 c.Assert(s.invalidCredential, jc.IsFalse) 370 // AliveContainers will return un-auth error 371 s.Client.Stub.SetErrors(errTestUnAuth) 372 373 // the call to Instances takes care of updating invalid credential details 374 _, err := s.Env.InstanceAvailabilityZoneNames(s.callCtx, []instance.Id{"not-valid"}) 375 c.Assert(err, gc.ErrorMatches, ".*not authorized") 376 c.Assert(s.invalidCredential, jc.IsTrue) 377 s.Stub.CheckCalls(c, []gitjujutesting.StubCall{ 378 {"AliveContainers", []interface{}{s.Prefix()}}, 379 }) 380 } 381 382 type environCloudProfileSuite struct { 383 lxd.EnvironSuite 384 385 svr *lxd.MockServer 386 cloudSpecEnv environs.CloudSpecSetter 387 } 388 389 var _ = gc.Suite(&environCloudProfileSuite{}) 390 391 func (s *environCloudProfileSuite) TestSetCloudSpecCreateProfile(c *gc.C) { 392 defer s.setup(c, nil).Finish() 393 s.expectHasProfileFalse("juju-controller") 394 s.expectCreateProfile("juju-controller", nil) 395 396 err := s.cloudSpecEnv.SetCloudSpec(stdcontext.TODO(), lxdCloudSpec()) 397 c.Assert(err, jc.ErrorIsNil) 398 } 399 400 func (s *environCloudProfileSuite) TestSetCloudSpecCreateProfileErrorSucceeds(c *gc.C) { 401 defer s.setup(c, nil).Finish() 402 s.expectForProfileCreateRace("juju-controller") 403 s.expectCreateProfile("juju-controller", errors.New("The profile already exists")) 404 405 err := s.cloudSpecEnv.SetCloudSpec(stdcontext.TODO(), lxdCloudSpec()) 406 c.Assert(err, jc.ErrorIsNil) 407 } 408 409 func (s *environCloudProfileSuite) TestSetCloudSpecUsesConfiguredProject(c *gc.C) { 410 defer s.setup(c, map[string]interface{}{"project": "my-project"}).Finish() 411 s.expectHasProfileFalse("juju-controller") 412 s.expectCreateProfile("juju-controller", nil) 413 414 err := s.cloudSpecEnv.SetCloudSpec(stdcontext.TODO(), lxdCloudSpec()) 415 c.Assert(err, jc.ErrorIsNil) 416 } 417 418 func (s *environCloudProfileSuite) setup(c *gc.C, cfgEdit map[string]interface{}) *gomock.Controller { 419 ctrl := gomock.NewController(c) 420 s.svr = lxd.NewMockServer(ctrl) 421 422 project, _ := cfgEdit["project"].(string) 423 cloudSpec := lxd.CloudSpec{ 424 CloudSpec: lxdCloudSpec(), 425 Project: project, 426 } 427 428 svrFactory := lxd.NewMockServerFactory(ctrl) 429 svrFactory.EXPECT().RemoteServer(cloudSpec).Return(s.svr, nil) 430 431 env, ok := s.NewEnvironWithServerFactory(c, svrFactory, cfgEdit).(environs.CloudSpecSetter) 432 c.Assert(ok, jc.IsTrue) 433 s.cloudSpecEnv = env 434 435 return ctrl 436 } 437 438 func (s *environCloudProfileSuite) expectForProfileCreateRace(name string) { 439 exp := s.svr.EXPECT() 440 gomock.InOrder( 441 exp.HasProfile(name).Return(false, nil), 442 exp.HasProfile(name).Return(true, nil), 443 ) 444 } 445 446 func (s *environCloudProfileSuite) expectHasProfileFalse(name string) { 447 s.svr.EXPECT().HasProfile(name).Return(false, nil) 448 } 449 450 func (s *environCloudProfileSuite) expectCreateProfile(name string, err error) { 451 s.svr.EXPECT().CreateProfileWithConfig(name, 452 map[string]string{ 453 "boot.autostart": "true", 454 "security.nesting": "true", 455 }).Return(err) 456 } 457 458 type environProfileSuite struct { 459 lxd.EnvironSuite 460 461 svr *lxd.MockServer 462 lxdEnv environs.LXDProfiler 463 } 464 465 var _ = gc.Suite(&environProfileSuite{}) 466 467 func (s *environProfileSuite) TestMaybeWriteLXDProfileYes(c *gc.C) { 468 defer s.setup(c, environscloudspec.CloudSpec{}).Finish() 469 470 profile := "testname" 471 s.expectMaybeWriteLXDProfile(false, profile) 472 473 err := s.lxdEnv.MaybeWriteLXDProfile(profile, lxdprofile.Profile{ 474 Config: map[string]string{ 475 "security.nesting": "true", 476 }, 477 Description: "test profile", 478 }) 479 c.Assert(err, jc.ErrorIsNil) 480 } 481 482 func (s *environProfileSuite) TestMaybeWriteLXDProfileNo(c *gc.C) { 483 defer s.setup(c, environscloudspec.CloudSpec{}).Finish() 484 485 profile := "testname" 486 s.expectMaybeWriteLXDProfile(true, profile) 487 488 err := s.lxdEnv.MaybeWriteLXDProfile(profile, lxdprofile.Profile{}) 489 c.Assert(err, jc.ErrorIsNil) 490 } 491 492 func (s *environProfileSuite) TestLXDProfileNames(c *gc.C) { 493 defer s.setup(c, environscloudspec.CloudSpec{}).Finish() 494 495 exp := s.svr.EXPECT() 496 exp.GetContainerProfiles("testname").Return([]string{ 497 lxdprofile.Name("foo", "bar", 1), 498 }, nil) 499 500 result, err := s.lxdEnv.LXDProfileNames("testname") 501 c.Assert(err, jc.ErrorIsNil) 502 c.Assert(result, jc.DeepEquals, []string{ 503 lxdprofile.Name("foo", "bar", 1), 504 }) 505 } 506 507 func (s *environProfileSuite) TestAssignLXDProfiles(c *gc.C) { 508 defer s.setup(c, environscloudspec.CloudSpec{}).Finish() 509 510 instId := "testme" 511 oldP := "old-profile" 512 newP := "new-profile" 513 expectedProfiles := []string{"default", "juju-default", newP} 514 s.expectAssignLXDProfiles(instId, oldP, newP, []string{}, expectedProfiles, nil) 515 516 obtained, err := s.lxdEnv.AssignLXDProfiles(instId, expectedProfiles, []lxdprofile.ProfilePost{ 517 { 518 Name: oldP, 519 Profile: nil, 520 }, { 521 Name: newP, 522 Profile: &lxdprofile.Profile{ 523 Config: map[string]string{ 524 "security.nesting": "true", 525 }, 526 Description: "test profile", 527 }, 528 }, 529 }) 530 c.Assert(err, jc.ErrorIsNil) 531 c.Assert(obtained, gc.DeepEquals, expectedProfiles) 532 } 533 534 func (s *environProfileSuite) TestAssignLXDProfilesErrorReturnsCurrent(c *gc.C) { 535 defer s.setup(c, environscloudspec.CloudSpec{}).Finish() 536 537 instId := "testme" 538 oldP := "old-profile" 539 newP := "new-profile" 540 expectedProfiles := []string{"default", "juju-default", oldP} 541 newProfiles := []string{"default", "juju-default", newP} 542 expectedErr := "fail UpdateContainerProfiles" 543 s.expectAssignLXDProfiles(instId, oldP, newP, expectedProfiles, newProfiles, errors.New(expectedErr)) 544 545 obtained, err := s.lxdEnv.AssignLXDProfiles(instId, newProfiles, []lxdprofile.ProfilePost{ 546 { 547 Name: oldP, 548 Profile: nil, 549 }, { 550 Name: newP, 551 Profile: &lxdprofile.Profile{ 552 Config: map[string]string{ 553 "security.nesting": "true", 554 }, 555 Description: "test profile", 556 }, 557 }, 558 }) 559 c.Assert(err, gc.ErrorMatches, expectedErr) 560 c.Assert(obtained, gc.DeepEquals, []string{"default", "juju-default", oldP}) 561 } 562 563 func (s *environProfileSuite) TestDetectCorrectHardwareEndpointIPOnly(c *gc.C) { 564 defer s.setup(c, environscloudspec.CloudSpec{ 565 Endpoint: "1.1.1.1", 566 }).Finish() 567 568 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 569 c.Assert(supported, jc.IsTrue) 570 571 hc, err := detector.DetectHardware() 572 c.Assert(err, gc.IsNil) 573 // 1.1.1.1 is not a local IP address, so we don't set ARCH in hc 574 c.Assert(hc, gc.IsNil) 575 } 576 577 func (s *environProfileSuite) TestDetectCorrectHardwareEndpointIPPort(c *gc.C) { 578 defer s.setup(c, environscloudspec.CloudSpec{ 579 Endpoint: "1.1.1.1:8888", 580 }).Finish() 581 582 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 583 c.Assert(supported, jc.IsTrue) 584 585 hc, err := detector.DetectHardware() 586 c.Assert(err, gc.IsNil) 587 // 1.1.1.1 is not a local IP address, so we don't set ARCH in hc 588 c.Assert(hc, gc.IsNil) 589 } 590 591 func (s *environProfileSuite) TestDetectCorrectHardwareEndpointSchemeIPPort(c *gc.C) { 592 defer s.setup(c, environscloudspec.CloudSpec{ 593 Endpoint: "http://1.1.1.1:8888", 594 }).Finish() 595 596 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 597 c.Assert(supported, jc.IsTrue) 598 599 hc, err := detector.DetectHardware() 600 c.Assert(err, gc.IsNil) 601 // 1.1.1.1 is not a local IP address, so we don't set ARCH in hc 602 c.Assert(hc, gc.IsNil) 603 } 604 605 func (s *environProfileSuite) TestDetectCorrectHardwareEndpointHostOnly(c *gc.C) { 606 defer s.setup(c, environscloudspec.CloudSpec{ 607 Endpoint: "localhost", 608 }).Finish() 609 610 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 611 c.Assert(supported, jc.IsTrue) 612 613 hc, err := detector.DetectHardware() 614 c.Assert(err, gc.IsNil) 615 // 1.1.1.1 is not a local IP address, so we don't set ARCH in hc 616 c.Assert(hc, gc.IsNil) 617 } 618 619 func (s *environProfileSuite) TestDetectCorrectHardwareEndpointHostPort(c *gc.C) { 620 defer s.setup(c, environscloudspec.CloudSpec{ 621 Endpoint: "localhost:8888", 622 }).Finish() 623 624 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 625 c.Assert(supported, jc.IsTrue) 626 627 hc, err := detector.DetectHardware() 628 c.Assert(err, gc.IsNil) 629 // localhost is not considered as a local IP address, so we don't set ARCH in hc 630 c.Assert(hc, gc.IsNil) 631 } 632 633 func (s *environProfileSuite) TestDetectCorrectHardwareEndpointSchemeHostPort(c *gc.C) { 634 defer s.setup(c, environscloudspec.CloudSpec{ 635 Endpoint: "http://localhost:8888", 636 }).Finish() 637 638 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 639 c.Assert(supported, jc.IsTrue) 640 641 hc, err := detector.DetectHardware() 642 c.Assert(err, gc.IsNil) 643 // localhost is not considered as a local IP address, so we don't set ARCH in hc 644 c.Assert(hc, gc.IsNil) 645 } 646 647 func (s *environProfileSuite) TestDetectCorrectHardwareWrongEndpoint(c *gc.C) { 648 defer s.setup(c, environscloudspec.CloudSpec{ 649 Endpoint: "1.1:8888", 650 }).Finish() 651 652 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 653 c.Assert(supported, jc.IsTrue) 654 655 hc, err := detector.DetectHardware() 656 // the endpoint is wrongly formatted but we don't return an error, that 657 // would mean we are stopping the bootstrap 658 c.Assert(err, gc.IsNil) 659 c.Assert(hc, gc.IsNil) 660 } 661 662 func (s *environProfileSuite) TestDetectCorrectHardwareEmptyEndpoint(c *gc.C) { 663 defer s.setup(c, environscloudspec.CloudSpec{ 664 Endpoint: "", 665 }).Finish() 666 667 detector, supported := s.lxdEnv.(environs.HardwareCharacteristicsDetector) 668 c.Assert(supported, jc.IsTrue) 669 670 hc, err := detector.DetectHardware() 671 // the endpoint is wrongly formatted but we don't return an error, that 672 // would mean we are stopping the bootstrap 673 c.Assert(err, gc.IsNil) 674 c.Assert(hc, gc.IsNil) 675 } 676 677 func (s *environProfileSuite) setup(c *gc.C, cloudSpec environscloudspec.CloudSpec) *gomock.Controller { 678 ctrl := gomock.NewController(c) 679 s.svr = lxd.NewMockServer(ctrl) 680 lxdEnv, ok := s.NewEnviron(c, s.svr, nil, cloudSpec).(environs.LXDProfiler) 681 c.Assert(ok, jc.IsTrue) 682 s.lxdEnv = lxdEnv 683 684 return ctrl 685 } 686 687 func (s *environProfileSuite) expectMaybeWriteLXDProfile(hasProfile bool, name string) { 688 exp := s.svr.EXPECT() 689 exp.HasProfile(name).Return(hasProfile, nil) 690 if !hasProfile { 691 post := api.ProfilesPost{ 692 Name: name, 693 ProfilePut: api.ProfilePut{ 694 Config: map[string]string{ 695 "security.nesting": "true", 696 }, 697 Description: "test profile", 698 }, 699 } 700 exp.CreateProfile(post).Return(nil) 701 expProfile := api.Profile{ProfilePut: post.ProfilePut} 702 exp.GetProfile(name).Return(&expProfile, "etag", nil) 703 } 704 } 705 706 func (s *environProfileSuite) expectAssignLXDProfiles(instId, old, new string, oldProfiles, newProfiles []string, updateErr error) { 707 s.expectMaybeWriteLXDProfile(false, new) 708 exp := s.svr.EXPECT() 709 exp.UpdateContainerProfiles(instId, newProfiles).Return(updateErr) 710 if updateErr != nil { 711 exp.GetContainerProfiles(instId).Return(oldProfiles, nil) 712 return 713 } 714 if old != "" { 715 exp.DeleteProfile(old) 716 } 717 exp.GetContainerProfiles(instId).Return(newProfiles, nil) 718 }