github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/container/lxd/manager_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd_test 5 6 import ( 7 stdcontext "context" 8 "errors" 9 10 lxdclient "github.com/canonical/lxd/client" 11 lxdapi "github.com/canonical/lxd/shared/api" 12 "github.com/juju/names/v5" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/version/v2" 15 "go.uber.org/mock/gomock" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/api" 19 "github.com/juju/juju/cloudconfig/instancecfg" 20 "github.com/juju/juju/container" 21 "github.com/juju/juju/container/lxd" 22 lxdtesting "github.com/juju/juju/container/lxd/testing" 23 corebase "github.com/juju/juju/core/base" 24 "github.com/juju/juju/core/constraints" 25 "github.com/juju/juju/core/lxdprofile" 26 corenetwork "github.com/juju/juju/core/network" 27 "github.com/juju/juju/core/status" 28 "github.com/juju/juju/environs/config" 29 "github.com/juju/juju/environs/context" 30 "github.com/juju/juju/network" 31 coretesting "github.com/juju/juju/testing" 32 coretools "github.com/juju/juju/tools" 33 ) 34 35 type managerSuite struct { 36 lxdtesting.BaseSuite 37 38 cSvr *lxdtesting.MockInstanceServer 39 createRemoteOp *lxdtesting.MockRemoteOperation 40 deleteOp *lxdtesting.MockOperation 41 startOp *lxdtesting.MockOperation 42 stopOp *lxdtesting.MockOperation 43 updateOp *lxdtesting.MockOperation 44 manager container.Manager 45 } 46 47 var _ = gc.Suite(&managerSuite{}) 48 49 func (s *managerSuite) patch() { 50 lxd.PatchConnectRemote(s, map[string]lxdclient.ImageServer{"cloud-images.ubuntu.com": s.cSvr}) 51 lxd.PatchGenerateVirtualMACAddress(s) 52 } 53 54 func (s *managerSuite) makeManager(c *gc.C) { 55 s.makeManagerForConfig(c, getBaseConfig()) 56 } 57 58 func (s *managerSuite) makeManagerForConfig(c *gc.C, cfg container.ManagerConfig) { 59 manager, err := lxd.NewContainerManager(cfg, func() (*lxd.Server, error) { return lxd.NewServer(s.cSvr) }) 60 c.Assert(err, jc.ErrorIsNil) 61 s.manager = manager 62 } 63 64 func getBaseConfig() container.ManagerConfig { 65 return container.ManagerConfig{ 66 container.ConfigModelUUID: coretesting.ModelTag.Id(), 67 container.ConfigAvailabilityZone: "test-availability-zone", 68 config.ContainerImageStreamKey: "released", 69 } 70 } 71 72 func prepInstanceConfig(c *gc.C) *instancecfg.InstanceConfig { 73 apiInfo := &api.Info{ 74 Addrs: []string{"127.0.0.1:1337"}, 75 Password: "password", 76 Nonce: "nonce", 77 Tag: names.NewMachineTag("123"), 78 ModelTag: names.NewModelTag("3fe11b2c-ae46-4cd1-96ad-d34239f70daf"), 79 CACert: "foo", 80 } 81 icfg, err := instancecfg.NewInstanceConfig( 82 names.NewControllerTag("4e29484b-4ff3-417f-97c3-09d1bec98d5b"), 83 "123", 84 "nonce", 85 "imagestream", 86 corebase.MakeDefaultBase("ubuntu", "16.04"), 87 apiInfo, 88 ) 89 c.Assert(err, jc.ErrorIsNil) 90 91 err = instancecfg.PopulateInstanceConfig( 92 icfg, 93 "lxd", 94 "", 95 false, 96 instancecfg.ProxyConfiguration{}, 97 false, 98 false, 99 nil, 100 nil, 101 ) 102 c.Assert(err, jc.ErrorIsNil) 103 104 list := coretools.List{ 105 &coretools.Tools{Version: version.MustParseBinary("2.3.4-ubuntu-amd64")}, 106 } 107 err = icfg.SetTools(list) 108 c.Assert(err, jc.ErrorIsNil) 109 return icfg 110 } 111 112 func prepNetworkConfig() *container.NetworkConfig { 113 return container.BridgeNetworkConfig(1500, corenetwork.InterfaceInfos{{ 114 InterfaceName: "eth0", 115 InterfaceType: corenetwork.EthernetDevice, 116 ConfigType: corenetwork.ConfigDHCP, 117 ParentInterfaceName: "eth0", 118 }}) 119 } 120 121 func (s *managerSuite) TestContainerCreateDestroy(c *gc.C) { 122 ctrl := s.setup(c) 123 defer ctrl.Finish() 124 s.patch() 125 s.makeManager(c) 126 127 iCfg := prepInstanceConfig(c) 128 hostName, err := s.manager.Namespace().Hostname(iCfg.MachineId) 129 c.Assert(err, jc.ErrorIsNil) 130 131 // Operation arrangements. 132 s.expectStartOp(ctrl) 133 s.expectStopOp(ctrl) 134 s.expectDeleteOp(ctrl) 135 136 exp := s.cSvr.EXPECT() 137 138 // Arrangements for the container creation. 139 s.expectCreateContainer(ctrl) 140 exp.UpdateInstanceState(hostName, lxdapi.InstanceStatePut{Action: "start", Timeout: -1}, "").Return(s.startOp, nil) 141 142 exp.GetInstanceState(hostName).Return( 143 &lxdapi.InstanceState{ 144 StatusCode: lxdapi.Running, 145 Network: map[string]lxdapi.InstanceStateNetwork{ 146 "fan0": { 147 Type: "fan", 148 }, 149 "eth0": { 150 HostName: "1lxd2-0", 151 Type: "bridged", 152 }, 153 }, 154 }, lxdtesting.ETag, nil).Times(2) 155 156 exp.GetInstance(hostName).Return(&lxdapi.Instance{Name: hostName, Type: "container"}, lxdtesting.ETag, nil) 157 158 // Arrangements for the container destruction. 159 stopReq := lxdapi.InstanceStatePut{ 160 Action: "stop", 161 Timeout: -1, 162 Stateful: false, 163 Force: true, 164 } 165 gomock.InOrder( 166 exp.UpdateInstanceState(hostName, stopReq, lxdtesting.ETag).Return(s.stopOp, nil), 167 exp.DeleteInstance(hostName).Return(s.deleteOp, nil), 168 ) 169 170 instance, hc, err := s.manager.CreateContainer( 171 stdcontext.Background(), iCfg, constraints.Value{}, corebase.MakeDefaultBase("ubuntu", "16.04"), prepNetworkConfig(), &container.StorageConfig{}, lxdtesting.NoOpCallback, 172 ) 173 c.Assert(err, jc.ErrorIsNil) 174 175 instanceId := instance.Id() 176 c.Check(string(instanceId), gc.Equals, hostName) 177 178 instanceStatus := instance.Status(context.NewEmptyCloudCallContext()) 179 c.Check(instanceStatus.Status, gc.Equals, status.Running) 180 c.Check(*hc.AvailabilityZone, gc.Equals, "test-availability-zone") 181 182 err = s.manager.DestroyContainer(instanceId) 183 c.Assert(err, jc.ErrorIsNil) 184 } 185 186 func (s *managerSuite) TestContainerCreateUpdateIPv4Network(c *gc.C) { 187 ctrl := s.setupWithExtensions(c, "network") 188 defer ctrl.Finish() 189 190 s.patch() 191 192 s.makeManager(c) 193 iCfg := prepInstanceConfig(c) 194 hostName, err := s.manager.Namespace().Hostname(iCfg.MachineId) 195 c.Assert(err, jc.ErrorIsNil) 196 197 exp := s.cSvr.EXPECT() 198 199 req := lxdapi.NetworkPut{ 200 Config: map[string]string{ 201 "ipv4.address": "auto", 202 "ipv4.nat": "true", 203 }, 204 } 205 gomock.InOrder( 206 exp.GetNetwork(network.DefaultLXDBridge).Return(&lxdapi.Network{}, lxdtesting.ETag, nil), 207 exp.UpdateNetwork(network.DefaultLXDBridge, req, lxdtesting.ETag).Return(nil), 208 ) 209 210 s.expectCreateContainer(ctrl) 211 s.expectStartOp(ctrl) 212 213 exp.UpdateInstanceState(hostName, lxdapi.InstanceStatePut{Action: "start", Timeout: -1}, "").Return(s.startOp, nil) 214 exp.GetInstance(hostName).Return(&lxdapi.Instance{Name: hostName, Type: "container"}, lxdtesting.ETag, nil) 215 216 // Supplying config for a single device with default bridge and without a 217 // CIDR will cause the default bridge to be updated with IPv4 config. 218 netConfig := container.BridgeNetworkConfig(1500, corenetwork.InterfaceInfos{{ 219 InterfaceName: "eth0", 220 InterfaceType: corenetwork.EthernetDevice, 221 ConfigType: corenetwork.ConfigDHCP, 222 ParentInterfaceName: network.DefaultLXDBridge, 223 }}) 224 _, _, err = s.manager.CreateContainer( 225 stdcontext.Background(), iCfg, constraints.Value{}, corebase.MakeDefaultBase("ubuntu", "16.04"), netConfig, &container.StorageConfig{}, lxdtesting.NoOpCallback, 226 ) 227 c.Assert(err, jc.ErrorIsNil) 228 } 229 230 func (s *managerSuite) TestCreateContainerCreateFailed(c *gc.C) { 231 ctrl := s.setup(c) 232 defer ctrl.Finish() 233 234 s.expectCreateRemoteOp(ctrl, &lxdapi.Operation{StatusCode: lxdapi.Failure, Err: "create failed"}) 235 236 image := lxdapi.Image{Filename: "this-is-our-image"} 237 s.expectGetImage(image, nil) 238 239 exp := s.cSvr.EXPECT() 240 exp.CreateInstanceFromImage(s.cSvr, image, gomock.Any()).Return(s.createRemoteOp, nil) 241 242 s.makeManager(c) 243 _, _, err := s.manager.CreateContainer( 244 stdcontext.Background(), 245 prepInstanceConfig(c), 246 constraints.Value{}, 247 corebase.MakeDefaultBase("ubuntu", "16.04"), 248 prepNetworkConfig(), 249 &container.StorageConfig{}, 250 lxdtesting.NoOpCallback, 251 ) 252 c.Assert(err, gc.ErrorMatches, ".*create failed") 253 } 254 255 func (s *managerSuite) TestCreateContainerSpecCreationError(c *gc.C) { 256 defer s.setup(c).Finish() 257 258 // When the local image acquisition fails, this will cause the remote 259 // connection attempt to fail. 260 // This is our error condition exit from manager.getContainerSpec. 261 lxd.PatchConnectRemote(s, map[string]lxdclient.ImageServer{}) 262 263 image := lxdapi.Image{Filename: "this-is-our-image"} 264 s.expectGetImage(image, errors.New("not here")) 265 266 s.makeManager(c) 267 _, _, err := s.manager.CreateContainer( 268 stdcontext.Background(), 269 prepInstanceConfig(c), 270 constraints.Value{}, 271 corebase.MakeDefaultBase("ubuntu", "16.04"), 272 prepNetworkConfig(), 273 &container.StorageConfig{}, 274 lxdtesting.NoOpCallback, 275 ) 276 c.Assert(err, gc.ErrorMatches, ".*unrecognized remote server") 277 } 278 279 func (s *managerSuite) TestCreateContainerStartFailed(c *gc.C) { 280 ctrl := s.setup(c) 281 defer ctrl.Finish() 282 s.patch() 283 s.makeManager(c) 284 285 iCfg := prepInstanceConfig(c) 286 hostName, err := s.manager.Namespace().Hostname(iCfg.MachineId) 287 c.Assert(err, jc.ErrorIsNil) 288 289 s.expectUpdateOp(ctrl, "", errors.New("start failed")) 290 s.expectDeleteOp(ctrl) 291 s.expectCreateContainer(ctrl) 292 293 exp := s.cSvr.EXPECT() 294 gomock.InOrder( 295 exp.UpdateInstanceState( 296 hostName, lxdapi.InstanceStatePut{Action: "start", Timeout: -1}, "").Return(s.updateOp, nil), 297 exp.GetInstanceState(hostName).Return(&lxdapi.InstanceState{StatusCode: lxdapi.Stopped}, lxdtesting.ETag, nil), 298 exp.DeleteInstance(hostName).Return(s.deleteOp, nil), 299 ) 300 301 _, _, err = s.manager.CreateContainer( 302 stdcontext.Background(), 303 iCfg, 304 constraints.Value{}, 305 corebase.MakeDefaultBase("ubuntu", "16.04"), 306 prepNetworkConfig(), 307 &container.StorageConfig{}, 308 lxdtesting.NoOpCallback, 309 ) 310 c.Assert(err, gc.ErrorMatches, ".*start failed") 311 } 312 313 func (s *managerSuite) TestListContainers(c *gc.C) { 314 defer s.setup(c).Finish() 315 s.makeManager(c) 316 317 prefix := s.manager.Namespace().Prefix() 318 wrongPrefix := prefix[:len(prefix)-1] + "j" 319 320 containers := []lxdapi.Instance{ 321 {Name: "foobar", Type: "container"}, 322 {Name: "definitely-not-a-juju-container", Type: "container"}, 323 {Name: wrongPrefix + "-0", Type: "container"}, 324 {Name: prefix + "-0", Type: "container"}, 325 {Name: "please-disperse", Type: "container"}, 326 {Name: prefix + "-1", Type: "container"}, 327 {Name: "nothing-to-see-here-please", Type: "container"}, 328 } 329 330 s.cSvr.EXPECT().GetInstances(lxdapi.InstanceTypeAny).Return(containers, nil) 331 332 result, err := s.manager.ListContainers() 333 c.Assert(err, jc.ErrorIsNil) 334 c.Check(result, gc.HasLen, 2) 335 c.Check(string(result[0].Id()), gc.Equals, prefix+"-0") 336 c.Check(string(result[1].Id()), gc.Equals, prefix+"-1") 337 } 338 339 func (s *managerSuite) TestIsInitialized(c *gc.C) { 340 mgr, err := lxd.NewContainerManager(getBaseConfig(), nil) 341 c.Assert(err, jc.ErrorIsNil) 342 343 c.Check(mgr.IsInitialized(), gc.Equals, lxd.SocketPath(lxd.IsUnixSocket) != "") 344 } 345 346 func (s *managerSuite) TestNetworkDevicesFromConfigWithEmptyParentDevice(c *gc.C) { 347 defer s.setup(c).Finish() 348 349 interfaces := corenetwork.InterfaceInfos{{ 350 InterfaceName: "eth1", 351 InterfaceType: "ethernet", 352 MACAddress: "aa:bb:cc:dd:ee:f1", 353 MTU: 9000, 354 }} 355 s.makeManager(c) 356 result, _, err := lxd.NetworkDevicesFromConfig(s.manager, &container.NetworkConfig{ 357 Interfaces: interfaces, 358 }) 359 360 c.Assert(err, gc.ErrorMatches, "parent interface name is empty") 361 c.Assert(result, gc.IsNil) 362 } 363 364 func (s *managerSuite) TestNetworkDevicesFromConfigWithParentDevice(c *gc.C) { 365 defer s.setup(c).Finish() 366 367 interfaces := corenetwork.InterfaceInfos{{ 368 ParentInterfaceName: "br-eth0", 369 InterfaceName: "eth0", 370 InterfaceType: "ethernet", 371 MACAddress: "aa:bb:cc:dd:ee:f0", 372 Addresses: corenetwork.ProviderAddresses{ 373 corenetwork.NewMachineAddress("", corenetwork.WithCIDR("10.10.0.0/24")).AsProviderAddress(), 374 }, 375 }} 376 377 expected := map[string]map[string]string{ 378 "eth0": { 379 "hwaddr": "aa:bb:cc:dd:ee:f0", 380 "name": "eth0", 381 "nictype": "bridged", 382 "parent": "br-eth0", 383 "type": "nic", 384 }, 385 } 386 387 s.makeManager(c) 388 result, unknown, err := lxd.NetworkDevicesFromConfig(s.manager, &container.NetworkConfig{ 389 Interfaces: interfaces, 390 }) 391 392 c.Assert(err, jc.ErrorIsNil) 393 c.Check(result, jc.DeepEquals, expected) 394 c.Check(unknown, gc.HasLen, 0) 395 } 396 397 func (s *managerSuite) TestNetworkDevicesFromConfigUnknownCIDR(c *gc.C) { 398 defer s.setup(c).Finish() 399 400 interfaces := corenetwork.InterfaceInfos{{ 401 ParentInterfaceName: "br-eth0", 402 InterfaceName: "eth0", 403 InterfaceType: "ethernet", 404 MACAddress: "aa:bb:cc:dd:ee:f0", 405 }} 406 407 s.makeManager(c) 408 _, unknown, err := lxd.NetworkDevicesFromConfig(s.manager, &container.NetworkConfig{ 409 Interfaces: interfaces, 410 }) 411 412 c.Assert(err, jc.ErrorIsNil) 413 c.Check(unknown, gc.DeepEquals, []string{"br-eth0"}) 414 } 415 416 func (s *managerSuite) TestNetworkDevicesFromConfigNoInputGetsProfileNICs(c *gc.C) { 417 defer s.setup(c).Finish() 418 s.patch() 419 420 s.cSvr.EXPECT().GetProfile("default").Return(defaultLegacyProfileWithNIC(), lxdtesting.ETag, nil) 421 422 s.makeManager(c) 423 result, _, err := lxd.NetworkDevicesFromConfig(s.manager, &container.NetworkConfig{}) 424 c.Assert(err, jc.ErrorIsNil) 425 426 exp := map[string]map[string]string{ 427 "eth0": { 428 "parent": network.DefaultLXDBridge, 429 "type": "nic", 430 "nictype": "bridged", 431 "hwaddr": "00:16:3e:00:00:00", 432 // NOTE: the host name will not be set because we get 433 // the NICs from the default profile. 434 }, 435 } 436 437 c.Check(result, gc.DeepEquals, exp) 438 } 439 440 func (s *managerSuite) TestGetImageSourcesDefaultConfig(c *gc.C) { 441 defer s.setup(c).Finish() 442 443 s.makeManager(c) 444 445 sources, err := lxd.GetImageSources(s.manager) 446 c.Assert(err, jc.ErrorIsNil) 447 c.Check(sources, gc.DeepEquals, []lxd.ServerSpec{lxd.CloudImagesRemote, lxd.CloudImagesDailyRemote, lxd.CloudImagesLinuxContainersRemote}) 448 } 449 450 func (s *managerSuite) TestGetImageSourcesNoDefaults(c *gc.C) { 451 defer s.setup(c).Finish() 452 453 cfg := getBaseConfig() 454 cfg[config.ContainerImageMetadataDefaultsDisabledKey] = "true" 455 s.makeManagerForConfig(c, cfg) 456 457 sources, err := lxd.GetImageSources(s.manager) 458 c.Assert(err, jc.ErrorIsNil) 459 c.Check(sources, gc.HasLen, 0) 460 } 461 462 func (s *managerSuite) TestGetImageSourcesNoDefaultsCustomURL(c *gc.C) { 463 defer s.setup(c).Finish() 464 465 cfg := getBaseConfig() 466 cfg[config.ContainerImageMetadataDefaultsDisabledKey] = "true" 467 cfg[config.ContainerImageMetadataURLKey] = "https://special.container.sauce" 468 s.makeManagerForConfig(c, cfg) 469 470 sources, err := lxd.GetImageSources(s.manager) 471 c.Assert(err, jc.ErrorIsNil) 472 expectedSources := []lxd.ServerSpec{ 473 { 474 Name: "special.container.sauce", 475 Host: "https://special.container.sauce", 476 Protocol: lxd.SimpleStreamsProtocol, 477 }, 478 } 479 c.Check(sources, gc.DeepEquals, expectedSources) 480 } 481 482 func (s *managerSuite) TestGetImageSourcesNonStandardStreamDefaultConfig(c *gc.C) { 483 defer s.setup(c).Finish() 484 485 cfg := getBaseConfig() 486 cfg[config.ContainerImageStreamKey] = "nope" 487 s.makeManagerForConfig(c, cfg) 488 489 sources, err := lxd.GetImageSources(s.manager) 490 c.Assert(err, jc.ErrorIsNil) 491 c.Check(sources, gc.DeepEquals, []lxd.ServerSpec{lxd.CloudImagesRemote, lxd.CloudImagesDailyRemote, lxd.CloudImagesLinuxContainersRemote}) 492 } 493 494 func (s *managerSuite) TestGetImageSourcesDailyOnly(c *gc.C) { 495 defer s.setup(c).Finish() 496 497 cfg := getBaseConfig() 498 cfg[config.ContainerImageStreamKey] = "daily" 499 s.makeManagerForConfig(c, cfg) 500 sources, err := lxd.GetImageSources(s.manager) 501 c.Assert(err, jc.ErrorIsNil) 502 c.Check(sources, gc.DeepEquals, []lxd.ServerSpec{lxd.CloudImagesDailyRemote, lxd.CloudImagesLinuxContainersRemote}) 503 } 504 505 func (s *managerSuite) TestGetImageSourcesImageMetadataURLExpectedHTTPSSources(c *gc.C) { 506 defer s.setup(c).Finish() 507 508 cfg := getBaseConfig() 509 cfg[config.ContainerImageMetadataURLKey] = "http://special.container.sauce" 510 s.makeManagerForConfig(c, cfg) 511 512 sources, err := lxd.GetImageSources(s.manager) 513 c.Assert(err, jc.ErrorIsNil) 514 515 expectedSources := []lxd.ServerSpec{ 516 { 517 Name: "special.container.sauce", 518 Host: "https://special.container.sauce", 519 Protocol: lxd.SimpleStreamsProtocol, 520 }, 521 lxd.CloudImagesRemote, 522 lxd.CloudImagesDailyRemote, 523 lxd.CloudImagesLinuxContainersRemote, 524 } 525 c.Check(sources, gc.DeepEquals, expectedSources) 526 } 527 528 func (s *managerSuite) TestGetImageSourcesImageMetadataURLDailyStream(c *gc.C) { 529 defer s.setup(c).Finish() 530 531 cfg := getBaseConfig() 532 cfg[config.ContainerImageMetadataURLKey] = "http://special.container.sauce" 533 cfg[config.ContainerImageStreamKey] = "daily" 534 s.makeManagerForConfig(c, cfg) 535 536 sources, err := lxd.GetImageSources(s.manager) 537 c.Assert(err, jc.ErrorIsNil) 538 539 expectedSources := []lxd.ServerSpec{ 540 { 541 Name: "special.container.sauce", 542 Host: "https://special.container.sauce", 543 Protocol: lxd.SimpleStreamsProtocol, 544 }, 545 lxd.CloudImagesDailyRemote, 546 lxd.CloudImagesLinuxContainersRemote, 547 } 548 c.Check(sources, gc.DeepEquals, expectedSources) 549 } 550 551 func (s *managerSuite) TestMaybeWriteLXDProfile(c *gc.C) { 552 defer s.setup(c).Finish() 553 554 s.makeManager(c) 555 proMgr, ok := s.manager.(container.LXDProfileManager) 556 c.Assert(ok, jc.IsTrue) 557 558 put := lxdprofile.Profile{ 559 Config: map[string]string{ 560 "security.nesting": "true", 561 "security.privileged": "true", 562 }, 563 Description: "lxd profile for testing", 564 Devices: map[string]map[string]string{ 565 "tun": { 566 "path": "/dev/net/tun", 567 "type": "unix-char", 568 }, 569 }, 570 } 571 post := lxdapi.ProfilesPost{ 572 ProfilePut: lxdapi.ProfilePut(put), 573 Name: "juju-default-lxd-0", 574 } 575 s.cSvr.EXPECT().CreateProfile(post).Return(nil) 576 s.cSvr.EXPECT().GetProfileNames().Return([]string{"default", "custom"}, nil) 577 expProfile := lxdapi.Profile{ProfilePut: lxdapi.ProfilePut(put)} 578 s.cSvr.EXPECT().GetProfile(post.Name).Return(&expProfile, "etag", nil) 579 580 err := proMgr.MaybeWriteLXDProfile("juju-default-lxd-0", put) 581 c.Assert(err, jc.ErrorIsNil) 582 } 583 584 func (s *managerSuite) TestAssignLXDProfiles(c *gc.C) { 585 ctrl := s.setup(c) 586 defer ctrl.Finish() 587 s.expectUpdateOp(ctrl, "Updating container", nil) 588 589 old := "old-profile" 590 new := "new-profile" 591 newProfiles := []string{"default", "juju-default", new} 592 put := lxdprofile.Profile{ 593 Config: map[string]string{ 594 "security.nesting": "true", 595 }, 596 Description: "test profile", 597 } 598 s.expectUpdateContainerProfiles(old, new, newProfiles, lxdapi.ProfilePut(put)) 599 profilePosts := []lxdprofile.ProfilePost{ 600 { 601 Name: old, 602 Profile: nil, 603 }, { 604 Name: new, 605 Profile: &put, 606 }, 607 } 608 609 s.makeManager(c) 610 proMgr, ok := s.manager.(container.LXDProfileManager) 611 c.Assert(ok, jc.IsTrue) 612 613 obtained, err := proMgr.AssignLXDProfiles("testme", newProfiles, profilePosts) 614 c.Assert(err, jc.ErrorIsNil) 615 c.Assert(obtained, gc.DeepEquals, newProfiles) 616 } 617 618 func (s *managerSuite) setup(c *gc.C) *gomock.Controller { 619 ctrl := gomock.NewController(c) 620 s.cSvr = s.NewMockServer(ctrl) 621 return ctrl 622 } 623 624 func (s *managerSuite) setupWithExtensions(c *gc.C, extensions ...string) *gomock.Controller { 625 ctrl := gomock.NewController(c) 626 s.cSvr = s.NewMockServerWithExtensions(ctrl, extensions...) 627 return ctrl 628 } 629 630 // expectCreateContainer is a convenience function for the expectations 631 // concerning a successful container creation based on a cached local 632 // image. 633 func (s *managerSuite) expectCreateContainer(ctrl *gomock.Controller) { 634 s.expectCreateRemoteOp(ctrl, &lxdapi.Operation{StatusCode: lxdapi.Success}) 635 636 image := lxdapi.Image{Filename: "this-is-our-image"} 637 s.expectGetImage(image, nil) 638 639 exp := s.cSvr.EXPECT() 640 exp.CreateInstanceFromImage(s.cSvr, image, gomock.Any()).Return(s.createRemoteOp, nil) 641 } 642 643 // expectCreateRemoteOp is a convenience function for the expectations 644 // concerning successful remote operations. 645 func (s *managerSuite) expectCreateRemoteOp(ctrl *gomock.Controller, op *lxdapi.Operation) { 646 s.createRemoteOp = lxdtesting.NewMockRemoteOperation(ctrl) 647 s.createRemoteOp.EXPECT().Wait().Return(nil).AnyTimes() 648 s.createRemoteOp.EXPECT().GetTarget().Return(op, nil) 649 } 650 651 // expectDeleteOp is a convenience function for the expectations 652 // concerning successful delete operations. 653 func (s *managerSuite) expectDeleteOp(ctrl *gomock.Controller) { 654 s.deleteOp = lxdtesting.NewMockOperation(ctrl) 655 s.deleteOp.EXPECT().Wait().Return(nil).AnyTimes() 656 } 657 658 // expectDeleteOp is a convenience function for the expectations 659 // concerning GetImage operations. 660 func (s *managerSuite) expectGetImage(image lxdapi.Image, getImageErr error) { 661 target := "foo-target" 662 alias := &lxdapi.ImageAliasesEntry{ImageAliasesEntryPut: lxdapi.ImageAliasesEntryPut{Target: target}} 663 664 exp := s.cSvr.EXPECT() 665 gomock.InOrder( 666 exp.GetImageAlias("juju/ubuntu@16.04/"+s.Arch()).Return(alias, lxdtesting.ETag, nil), 667 exp.GetImage(target).Return(&image, lxdtesting.ETag, getImageErr), 668 ) 669 } 670 671 // expectStartOp is a convenience function for the expectations 672 // concerning a successful start operation. 673 func (s *managerSuite) expectStartOp(ctrl *gomock.Controller) { 674 s.startOp = lxdtesting.NewMockOperation(ctrl) 675 s.startOp.EXPECT().Wait().Return(nil) 676 } 677 678 // expectStopOp is a convenience function for the expectations 679 // concerning successful stop operation. 680 func (s *managerSuite) expectStopOp(ctrl *gomock.Controller) { 681 s.stopOp = lxdtesting.NewMockOperation(ctrl) 682 s.stopOp.EXPECT().Wait().Return(nil) 683 } 684 685 // expectStopOp is a convenience function for the expectations 686 // concerning an update operation. 687 func (s *managerSuite) expectUpdateOp(ctrl *gomock.Controller, description string, waitErr error) { 688 s.updateOp = lxdtesting.NewMockOperation(ctrl) 689 s.updateOp.EXPECT().Wait().Return(waitErr) 690 if waitErr != nil { 691 return 692 } 693 s.updateOp.EXPECT().Get().Return(lxdapi.Operation{Description: description}) 694 } 695 696 func (s *managerSuite) expectUpdateContainerProfiles(old, new string, newProfiles []string, put lxdapi.ProfilePut) { 697 instId := "testme" 698 oldProfiles := []string{"default", "juju-default", old} 699 post := lxdapi.ProfilesPost{ 700 ProfilePut: put, 701 Name: new, 702 } 703 expProfile := lxdapi.Profile{ProfilePut: put} 704 cExp := s.cSvr.EXPECT() 705 gomock.InOrder( 706 cExp.GetProfileNames().Return(oldProfiles, nil), 707 cExp.CreateProfile(post).Return(nil), 708 cExp.GetProfile(post.Name).Return(&expProfile, "etag", nil), 709 cExp.GetInstance(instId).Return( 710 &lxdapi.Instance{ 711 InstancePut: lxdapi.InstancePut{ 712 Profiles: oldProfiles, 713 }, 714 }, "", nil), 715 cExp.UpdateInstance(instId, gomock.Any(), gomock.Any()).Return(s.updateOp, nil), 716 cExp.DeleteProfile(old).Return(nil), 717 cExp.GetInstance(instId).Return( 718 &lxdapi.Instance{ 719 InstancePut: lxdapi.InstancePut{ 720 Profiles: newProfiles, 721 }, 722 }, "", nil), 723 ) 724 }