github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/container/lxd/container_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd_test
     5  
     6  import (
     7  	"errors"
     8  
     9  	"github.com/golang/mock/gomock"
    10  	jc "github.com/juju/testing/checkers"
    11  	"github.com/juju/utils/arch"
    12  	"github.com/lxc/lxd/shared/api"
    13  	"github.com/lxc/lxd/shared/osarch"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/container/lxd"
    17  	lxdtesting "github.com/juju/juju/container/lxd/testing"
    18  	"github.com/juju/juju/core/constraints"
    19  	"github.com/juju/juju/environs/tags"
    20  	"github.com/juju/juju/network"
    21  )
    22  
    23  type containerSuite struct {
    24  	lxdtesting.BaseSuite
    25  }
    26  
    27  var _ = gc.Suite(&containerSuite{})
    28  
    29  func (s *containerSuite) TestContainerMetadata(c *gc.C) {
    30  	container := lxd.Container{}
    31  	container.Config = map[string]string{"user.juju-controller-uuid": "something"}
    32  	c.Check(container.Metadata(tags.JujuController), gc.Equals, "something")
    33  }
    34  
    35  func (s *containerSuite) TestContainerArch(c *gc.C) {
    36  	lxdArch, _ := osarch.ArchitectureName(osarch.ARCH_64BIT_INTEL_X86)
    37  	container := lxd.Container{}
    38  	container.Architecture = lxdArch
    39  	c.Check(container.Arch(), gc.Equals, arch.AMD64)
    40  }
    41  
    42  func (s *containerSuite) TestContainerCPUs(c *gc.C) {
    43  	container := lxd.Container{}
    44  	container.Config = map[string]string{"limits.cpu": "2"}
    45  	c.Check(container.CPUs(), gc.Equals, uint(2))
    46  }
    47  
    48  func (s *containerSuite) TestContainerMem(c *gc.C) {
    49  	container := lxd.Container{}
    50  
    51  	container.Config = map[string]string{"limits.memory": "1MiB"}
    52  	c.Check(int(container.Mem()), gc.Equals, 1)
    53  
    54  	container.Config = map[string]string{"limits.memory": "2GiB"}
    55  	c.Check(int(container.Mem()), gc.Equals, 2048)
    56  }
    57  
    58  func (s *containerSuite) TestContainerAddDiskNoDevices(c *gc.C) {
    59  	container := lxd.Container{}
    60  	err := container.AddDisk("root", "/", "source", "default", true)
    61  	c.Assert(err, jc.ErrorIsNil)
    62  
    63  	expected := map[string]string{
    64  		"path":     "/",
    65  		"source":   "source",
    66  		"pool":     "default",
    67  		"readonly": "true",
    68  	}
    69  	c.Check(container.Devices["root"], gc.DeepEquals, expected)
    70  }
    71  
    72  func (s *containerSuite) TestContainerAddDiskDevicePresentError(c *gc.C) {
    73  	container := lxd.Container{}
    74  	container.Name = "seeyounexttuesday"
    75  	container.Devices = map[string]map[string]string{"root": {}}
    76  
    77  	err := container.AddDisk("root", "/", "source", "default", true)
    78  	c.Check(err, gc.ErrorMatches, `container "seeyounexttuesday" already has a device "root"`)
    79  }
    80  
    81  func (s *containerSuite) TestFilterContainers(c *gc.C) {
    82  	ctrl := gomock.NewController(c)
    83  	defer ctrl.Finish()
    84  	cSvr := s.NewMockServer(ctrl)
    85  
    86  	matching := []api.Container{
    87  		{
    88  			Name:       "prefix-c1",
    89  			StatusCode: api.Starting,
    90  		},
    91  		{
    92  			Name:       "prefix-c2",
    93  			StatusCode: api.Stopped,
    94  		},
    95  	}
    96  	ret := append(matching, []api.Container{
    97  		{
    98  			Name:       "prefix-c3",
    99  			StatusCode: api.Started,
   100  		},
   101  		{
   102  			Name:       "not-prefix-c4",
   103  			StatusCode: api.Stopped,
   104  		},
   105  	}...)
   106  	cSvr.EXPECT().GetContainers().Return(ret, nil)
   107  
   108  	jujuSvr, err := lxd.NewServer(cSvr)
   109  	c.Assert(err, jc.ErrorIsNil)
   110  
   111  	filtered, err := jujuSvr.FilterContainers("prefix", "Starting", "Stopped")
   112  	c.Assert(err, jc.ErrorIsNil)
   113  
   114  	expected := make([]lxd.Container, len(matching))
   115  	for i, v := range matching {
   116  		expected[i] = lxd.Container{v}
   117  	}
   118  
   119  	c.Check(filtered, gc.DeepEquals, expected)
   120  }
   121  
   122  func (s *containerSuite) TestAliveContainers(c *gc.C) {
   123  	ctrl := gomock.NewController(c)
   124  	defer ctrl.Finish()
   125  	cSvr := s.NewMockServer(ctrl)
   126  
   127  	matching := []api.Container{
   128  		{
   129  			Name:       "c1",
   130  			StatusCode: api.Starting,
   131  		},
   132  		{
   133  			Name:       "c2",
   134  			StatusCode: api.Stopped,
   135  		},
   136  		{
   137  			Name:       "c3",
   138  			StatusCode: api.Running,
   139  		},
   140  	}
   141  	ret := append(matching, api.Container{
   142  		Name:       "c4",
   143  		StatusCode: api.Frozen,
   144  	})
   145  	cSvr.EXPECT().GetContainers().Return(ret, nil)
   146  
   147  	jujuSvr, err := lxd.NewServer(cSvr)
   148  	c.Assert(err, jc.ErrorIsNil)
   149  
   150  	filtered, err := jujuSvr.AliveContainers("")
   151  	c.Assert(err, jc.ErrorIsNil)
   152  
   153  	expected := make([]lxd.Container, len(matching))
   154  	for i, v := range matching {
   155  		expected[i] = lxd.Container{v}
   156  	}
   157  	c.Check(filtered, gc.DeepEquals, expected)
   158  }
   159  
   160  func (s *containerSuite) TestContainerAddresses(c *gc.C) {
   161  	ctrl := gomock.NewController(c)
   162  	defer ctrl.Finish()
   163  	cSvr := s.NewMockServer(ctrl)
   164  
   165  	state := api.ContainerState{
   166  		Network: map[string]api.ContainerStateNetwork{
   167  			"eth0": {
   168  				Addresses: []api.ContainerStateNetworkAddress{
   169  					{
   170  						Family:  "inet",
   171  						Address: "10.0.8.173",
   172  						Netmask: "24",
   173  						Scope:   "global",
   174  					},
   175  					{
   176  						Family:  "inet6",
   177  						Address: "fe80::216:3eff:fe3b:e582",
   178  						Netmask: "64",
   179  						Scope:   "link",
   180  					},
   181  				},
   182  			},
   183  			"lo": {
   184  				Addresses: []api.ContainerStateNetworkAddress{
   185  					{
   186  						Family:  "inet",
   187  						Address: "127.0.0.1",
   188  						Netmask: "8",
   189  						Scope:   "local",
   190  					},
   191  					{
   192  						Family:  "inet6",
   193  						Address: "::1",
   194  						Netmask: "128",
   195  						Scope:   "local",
   196  					},
   197  				},
   198  			},
   199  			"lxcbr0": {
   200  				Addresses: []api.ContainerStateNetworkAddress{
   201  					{
   202  						Family:  "inet",
   203  						Address: "10.0.5.12",
   204  						Netmask: "24",
   205  						Scope:   "global",
   206  					},
   207  					{
   208  						Family:  "inet6",
   209  						Address: "fe80::216:3eff:fe3b:e432",
   210  						Netmask: "64",
   211  						Scope:   "link",
   212  					},
   213  				},
   214  			},
   215  			"lxdbr0": {
   216  				Addresses: []api.ContainerStateNetworkAddress{
   217  					{
   218  						Family:  "inet",
   219  						Address: "10.0.6.17",
   220  						Netmask: "24",
   221  						Scope:   "global",
   222  					},
   223  					{
   224  						Family:  "inet6",
   225  						Address: "fe80::5c9b:b2ff:feaf:4cf2",
   226  						Netmask: "64",
   227  						Scope:   "link",
   228  					},
   229  				},
   230  			},
   231  		},
   232  	}
   233  	cSvr.EXPECT().GetContainerState("c1").Return(&state, lxdtesting.ETag, nil)
   234  
   235  	jujuSvr, err := lxd.NewServer(cSvr)
   236  	c.Assert(err, jc.ErrorIsNil)
   237  
   238  	addrs, err := jujuSvr.ContainerAddresses("c1")
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	expected := []network.Address{
   242  		{
   243  			Value: "10.0.8.173",
   244  			Type:  network.IPv4Address,
   245  			Scope: network.ScopeCloudLocal,
   246  		},
   247  	}
   248  	c.Check(addrs, gc.DeepEquals, expected)
   249  }
   250  
   251  func (s *containerSuite) TestCreateContainerFromSpecSuccess(c *gc.C) {
   252  	ctrl := gomock.NewController(c)
   253  	defer ctrl.Finish()
   254  	cSvr := s.NewMockServer(ctrl)
   255  
   256  	// Operation arrangements.
   257  	createOp := lxdtesting.NewMockRemoteOperation(ctrl)
   258  	createOp.EXPECT().Wait().Return(nil)
   259  	createOp.EXPECT().GetTarget().Return(&api.Operation{StatusCode: api.Success}, nil)
   260  
   261  	startOp := lxdtesting.NewMockOperation(ctrl)
   262  	startOp.EXPECT().Wait().Return(nil)
   263  
   264  	// Request data.
   265  	image := api.Image{Filename: "container-image"}
   266  	spec := lxd.ContainerSpec{
   267  		Name: "c1",
   268  		Image: lxd.SourcedImage{
   269  			Image:     &image,
   270  			LXDServer: cSvr,
   271  		},
   272  		Profiles: []string{"default"},
   273  		Devices: map[string]map[string]string{
   274  			"eth0": {
   275  				"parent":  network.DefaultLXDBridge,
   276  				"type":    "nic",
   277  				"nictype": "bridged",
   278  			},
   279  		},
   280  		Config: map[string]string{
   281  			"limits.cpu": "2",
   282  		},
   283  	}
   284  
   285  	createReq := api.ContainersPost{
   286  		Name: spec.Name,
   287  		ContainerPut: api.ContainerPut{
   288  			Profiles:  spec.Profiles,
   289  			Devices:   spec.Devices,
   290  			Config:    spec.Config,
   291  			Ephemeral: false,
   292  		},
   293  	}
   294  
   295  	startReq := api.ContainerStatePut{
   296  		Action:   "start",
   297  		Timeout:  -1,
   298  		Force:    false,
   299  		Stateful: false,
   300  	}
   301  
   302  	// Container created, started and returned.
   303  	exp := cSvr.EXPECT()
   304  	gomock.InOrder(
   305  		exp.CreateContainerFromImage(cSvr, image, createReq).Return(createOp, nil),
   306  		exp.UpdateContainerState(spec.Name, startReq, "").Return(startOp, nil),
   307  		exp.GetContainer(spec.Name).Return(&api.Container{}, lxdtesting.ETag, nil),
   308  	)
   309  
   310  	jujuSvr, err := lxd.NewServer(cSvr)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  
   313  	container, err := jujuSvr.CreateContainerFromSpec(spec)
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	c.Check(container, gc.NotNil)
   316  }
   317  
   318  func (s *containerSuite) TestCreateContainerFromSpecStartFailed(c *gc.C) {
   319  	ctrl := gomock.NewController(c)
   320  	defer ctrl.Finish()
   321  	cSvr := s.NewMockServer(ctrl)
   322  
   323  	// Operation arrangements.
   324  	createOp := lxdtesting.NewMockRemoteOperation(ctrl)
   325  	createOp.EXPECT().Wait().Return(nil)
   326  	createOp.EXPECT().GetTarget().Return(&api.Operation{StatusCode: api.Success}, nil)
   327  
   328  	deleteOp := lxdtesting.NewMockOperation(ctrl)
   329  	deleteOp.EXPECT().Wait().Return(nil)
   330  
   331  	// Request data.
   332  	image := api.Image{Filename: "container-image"}
   333  	spec := lxd.ContainerSpec{
   334  		Name: "c1",
   335  		Image: lxd.SourcedImage{
   336  			Image:     &image,
   337  			LXDServer: cSvr,
   338  		},
   339  		Profiles: []string{"default"},
   340  		Devices: map[string]map[string]string{
   341  			"eth0": {
   342  				"parent":  network.DefaultLXDBridge,
   343  				"type":    "nic",
   344  				"nictype": "bridged",
   345  			},
   346  		},
   347  		Config: map[string]string{
   348  			"limits.cpu": "2",
   349  		},
   350  	}
   351  
   352  	createReq := api.ContainersPost{
   353  		Name: spec.Name,
   354  		ContainerPut: api.ContainerPut{
   355  			Profiles:  spec.Profiles,
   356  			Devices:   spec.Devices,
   357  			Config:    spec.Config,
   358  			Ephemeral: false,
   359  		},
   360  	}
   361  
   362  	startReq := api.ContainerStatePut{
   363  		Action:   "start",
   364  		Timeout:  -1,
   365  		Force:    false,
   366  		Stateful: false,
   367  	}
   368  
   369  	// Container created, starting fails, container state checked, container deleted.
   370  	exp := cSvr.EXPECT()
   371  	gomock.InOrder(
   372  		exp.CreateContainerFromImage(cSvr, image, createReq).Return(createOp, nil),
   373  		exp.UpdateContainerState(spec.Name, startReq, "").Return(nil, errors.New("start failed")),
   374  		exp.GetContainerState(spec.Name).Return(
   375  			&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil),
   376  		exp.DeleteContainer(spec.Name).Return(deleteOp, nil),
   377  	)
   378  
   379  	jujuSvr, err := lxd.NewServer(cSvr)
   380  	c.Assert(err, jc.ErrorIsNil)
   381  
   382  	container, err := jujuSvr.CreateContainerFromSpec(spec)
   383  	c.Assert(err, gc.ErrorMatches, "start failed")
   384  	c.Check(container, gc.IsNil)
   385  }
   386  
   387  func (s *containerSuite) TestRemoveContainersSuccess(c *gc.C) {
   388  	ctrl := gomock.NewController(c)
   389  	defer ctrl.Finish()
   390  	cSvr := s.NewMockServer(ctrl)
   391  
   392  	stopOp := lxdtesting.NewMockOperation(ctrl)
   393  	stopOp.EXPECT().Wait().Return(nil)
   394  
   395  	deleteOp := lxdtesting.NewMockOperation(ctrl)
   396  	deleteOp.EXPECT().Wait().Return(nil).Times(2)
   397  
   398  	stopReq := api.ContainerStatePut{
   399  		Action:   "stop",
   400  		Timeout:  -1,
   401  		Force:    true,
   402  		Stateful: false,
   403  	}
   404  
   405  	// Container c1 is already stopped. Container c2 is started and stopped before deletion.
   406  	exp := cSvr.EXPECT()
   407  	exp.GetContainerState("c1").Return(&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil)
   408  	exp.DeleteContainer("c1").Return(deleteOp, nil)
   409  	exp.GetContainerState("c2").Return(&api.ContainerState{StatusCode: api.Started}, lxdtesting.ETag, nil)
   410  	exp.UpdateContainerState("c2", stopReq, lxdtesting.ETag).Return(stopOp, nil)
   411  	exp.DeleteContainer("c2").Return(deleteOp, nil)
   412  
   413  	jujuSvr, err := lxd.NewServer(cSvr)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  
   416  	err = jujuSvr.RemoveContainers([]string{"c1", "c2"})
   417  	c.Assert(err, jc.ErrorIsNil)
   418  }
   419  
   420  func (s *containerSuite) TestRemoveContainersPartialFailure(c *gc.C) {
   421  	ctrl := gomock.NewController(c)
   422  	defer ctrl.Finish()
   423  	cSvr := s.NewMockServer(ctrl)
   424  
   425  	stopOp := lxdtesting.NewMockOperation(ctrl)
   426  	stopOp.EXPECT().Wait().Return(nil)
   427  
   428  	deleteOp := lxdtesting.NewMockOperation(ctrl)
   429  	deleteOp.EXPECT().Wait().Return(nil)
   430  
   431  	stopReq := api.ContainerStatePut{
   432  		Action:   "stop",
   433  		Timeout:  -1,
   434  		Force:    true,
   435  		Stateful: false,
   436  	}
   437  
   438  	// Container c1, c2 already stopped, but delete fails. Container c2 is started and stopped before deletion.
   439  	exp := cSvr.EXPECT()
   440  	exp.GetContainerState("c1").Return(&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil)
   441  	exp.DeleteContainer("c1").Return(nil, errors.New("deletion failed"))
   442  	exp.GetContainerState("c2").Return(&api.ContainerState{StatusCode: api.Stopped}, lxdtesting.ETag, nil)
   443  	exp.DeleteContainer("c2").Return(nil, errors.New("deletion failed"))
   444  	exp.GetContainerState("c3").Return(&api.ContainerState{StatusCode: api.Started}, lxdtesting.ETag, nil)
   445  	exp.UpdateContainerState("c3", stopReq, lxdtesting.ETag).Return(stopOp, nil)
   446  	exp.DeleteContainer("c3").Return(deleteOp, nil)
   447  
   448  	jujuSvr, err := lxd.NewServer(cSvr)
   449  	c.Assert(err, jc.ErrorIsNil)
   450  
   451  	err = jujuSvr.RemoveContainers([]string{"c1", "c2", "c3"})
   452  	c.Assert(err, gc.ErrorMatches, "failed to remove containers: c1, c2")
   453  }
   454  
   455  func (s *managerSuite) TestSpecApplyConstraints(c *gc.C) {
   456  	mem := uint64(2046)
   457  	cores := uint64(4)
   458  	instType := "t2.micro"
   459  
   460  	cons := constraints.Value{
   461  		Mem:          &mem,
   462  		CpuCores:     &cores,
   463  		InstanceType: &instType,
   464  	}
   465  
   466  	spec := lxd.ContainerSpec{
   467  		Config: map[string]string{lxd.AutoStartKey: "true"},
   468  	}
   469  	spec.ApplyConstraints(cons)
   470  
   471  	exp := map[string]string{
   472  		lxd.AutoStartKey: "true",
   473  		"limits.memory":  "2046MiB",
   474  		"limits.cpu":     "4",
   475  	}
   476  	c.Check(spec.Config, gc.DeepEquals, exp)
   477  	c.Check(spec.InstanceType, gc.Equals, instType)
   478  }