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  }