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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build linux
     5  
     6  package lxd
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net"
    13  	"os/exec"
    14  	"runtime"
    15  
    16  	"github.com/juju/packaging/commands"
    17  	"github.com/juju/packaging/manager"
    18  	"github.com/juju/proxy"
    19  	"github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	"github.com/lxc/lxd/shared/api"
    22  	gc "gopkg.in/check.v1"
    23  
    24  	"github.com/golang/mock/gomock"
    25  	lxdtesting "github.com/juju/juju/container/lxd/testing"
    26  	coretesting "github.com/juju/juju/testing"
    27  	"github.com/lxc/lxd/client"
    28  )
    29  
    30  type InitialiserSuite struct {
    31  	coretesting.BaseSuite
    32  	calledCmds []string
    33  	testing.PatchExecHelper
    34  }
    35  
    36  var _ = gc.Suite(&InitialiserSuite{})
    37  
    38  const lxdBridgeContent = `# WARNING: Don't modify this file by hand, it is generated by debconf!
    39  # To update those values, please run "dpkg-reconfigure lxd"
    40  
    41  # Whether to setup a new bridge
    42  USE_LXD_BRIDGE="true"
    43  EXISTING_BRIDGE=""
    44  
    45  # Bridge name
    46  LXD_BRIDGE="lxdbr0"
    47  
    48  # dnsmasq configuration path
    49  LXD_CONFILE=""
    50  
    51  # dnsmasq domain
    52  LXD_DOMAIN="lxd"
    53  
    54  # IPv4
    55  LXD_IPV4_ADDR="10.0.4.1"
    56  LXD_IPV4_NETMASK="255.255.255.0"
    57  LXD_IPV4_NETWORK="10.0.4.1/24"
    58  LXD_IPV4_DHCP_RANGE="10.0.4.2,10.0.4.100"
    59  LXD_IPV4_DHCP_MAX="50"
    60  LXD_IPV4_NAT="true"
    61  
    62  # IPv6
    63  LXD_IPV6_ADDR="2001:470:b2b5:9999::1"
    64  LXD_IPV6_MASK="64"
    65  LXD_IPV6_NETWORK="2001:470:b2b5:9999::1/64"
    66  LXD_IPV6_NAT="true"
    67  
    68  # Proxy server
    69  LXD_IPV6_PROXY="true"
    70  `
    71  
    72  func (s *InitialiserSuite) PatchForProxyUpdate(c *gc.C, svr lxd.ContainerServer, lxdIsRunning bool) {
    73  	s.PatchValue(&ConnectLocal, func() (lxd.ContainerServer, error) {
    74  		return svr, nil
    75  	})
    76  
    77  	s.PatchValue(&IsRunningLocally, func() (bool, error) {
    78  		return lxdIsRunning, nil
    79  	})
    80  }
    81  
    82  func (s *InitialiserSuite) SetUpTest(c *gc.C) {
    83  	s.BaseSuite.SetUpTest(c)
    84  	s.calledCmds = []string{}
    85  	s.PatchValue(&manager.RunCommandWithRetry, getMockRunCommandWithRetry(&s.calledCmds))
    86  	s.PatchValue(&configureLXDBridge, func() error { return nil })
    87  
    88  	nonRandomizedOctetRange := func() []int {
    89  		// chosen by fair dice roll
    90  		// guaranteed to be random :)
    91  		// intentionally not random to allow for deterministic tests
    92  		return []int{4, 5, 6, 7, 8}
    93  	}
    94  	s.PatchValue(&randomizedOctetRange, nonRandomizedOctetRange)
    95  	// Fake the lxc executable for all the tests.
    96  	testing.PatchExecutableAsEchoArgs(c, s, "lxc")
    97  	testing.PatchExecutableAsEchoArgs(c, s, "lxd")
    98  }
    99  
   100  // getMockRunCommandWithRetry is a helper function which returns a function
   101  // with an identical signature to manager.RunCommandWithRetry which saves each
   102  // command it receives in a slice and always returns no output, error code 0
   103  // and a nil error.
   104  func getMockRunCommandWithRetry(calledCmds *[]string) func(string, func(string) error) (string, int, error) {
   105  	return func(cmd string, fatalError func(string) error) (string, int, error) {
   106  		*calledCmds = append(*calledCmds, cmd)
   107  		return "", 0, nil
   108  	}
   109  }
   110  
   111  func (s *InitialiserSuite) TestLTSSeriesPackages(c *gc.C) {
   112  	paccmder, err := commands.NewPackageCommander("trusty")
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	PatchLXDViaSnap(s, false)
   116  	PatchHostSeries(s, "trusty")
   117  
   118  	err = NewContainerInitialiser().Initialise()
   119  	c.Assert(err, jc.ErrorIsNil)
   120  
   121  	c.Assert(s.calledCmds, gc.DeepEquals, []string{
   122  		paccmder.InstallCmd("--target-release", "trusty-backports", "lxd"),
   123  	})
   124  }
   125  
   126  func (s *InitialiserSuite) TestSnapInstalledNoAptInstall(c *gc.C) {
   127  	PatchLXDViaSnap(s, true)
   128  	PatchHostSeries(s, "cosmic")
   129  
   130  	err := NewContainerInitialiser().Initialise()
   131  	c.Assert(err, jc.ErrorIsNil)
   132  
   133  	c.Assert(s.calledCmds, gc.DeepEquals, []string{})
   134  }
   135  
   136  func (s *InitialiserSuite) TestNoSeriesPackages(c *gc.C) {
   137  	PatchLXDViaSnap(s, false)
   138  
   139  	// Here we want to test for any other series whilst avoiding the
   140  	// possibility of hitting a cloud archive-requiring release.
   141  	// As such, we simply pass an empty series.
   142  	PatchHostSeries(s, "")
   143  
   144  	paccmder, err := commands.NewPackageCommander("xenial")
   145  	c.Assert(err, jc.ErrorIsNil)
   146  
   147  	err = NewContainerInitialiser().Initialise()
   148  	c.Assert(err, jc.ErrorIsNil)
   149  
   150  	c.Assert(s.calledCmds, gc.DeepEquals, []string{
   151  		paccmder.InstallCmd("lxd"),
   152  	})
   153  }
   154  
   155  func (s *InitialiserSuite) TestLXDInitBionic(c *gc.C) {
   156  	s.patchDF100GB()
   157  	PatchHostSeries(s, "bionic")
   158  
   159  	container := NewContainerInitialiser()
   160  	err := container.Initialise()
   161  	c.Assert(err, jc.ErrorIsNil)
   162  
   163  	testing.AssertEchoArgs(c, "lxd", "init", "--auto")
   164  }
   165  
   166  func (s *InitialiserSuite) TestLXDInitTrusty(c *gc.C) {
   167  	s.patchDF100GB()
   168  	PatchHostSeries(s, "trusty")
   169  
   170  	container := NewContainerInitialiser()
   171  	err := container.Initialise()
   172  	c.Assert(err, jc.ErrorIsNil)
   173  
   174  	// Check that our patched call has no recorded args.
   175  	execPath, err := exec.LookPath("lxd")
   176  	c.Assert(err, jc.ErrorIsNil)
   177  	_, err = ioutil.ReadFile(execPath + ".out")
   178  	c.Assert(err, gc.ErrorMatches, "*. no such file or directory$")
   179  }
   180  
   181  func (s *InitialiserSuite) TestLXDAlreadyInitialized(c *gc.C) {
   182  	s.patchDF100GB()
   183  	PatchHostSeries(s, "trusty")
   184  
   185  	container := NewContainerInitialiser()
   186  	cont, ok := container.(*containerInitialiser)
   187  	if !ok {
   188  		c.Fatalf("Unexpected type of container initialized: %T", container)
   189  	}
   190  	cont.getExecCommand = s.PatchExecHelper.GetExecCommand(testing.PatchExecConfig{
   191  		Stderr: `LXD init cannot be used at this time.
   192  However if all you want to do is reconfigure the network,
   193  you can still do so by running "sudo dpkg-reconfigure -p medium lxd"
   194  
   195  error: You have existing containers or images. lxd init requires an empty LXD.`,
   196  		ExitCode: 1,
   197  	})
   198  
   199  	// the above error should be ignored by the code that calls lxd init.
   200  	err := container.Initialise()
   201  	c.Assert(err, jc.ErrorIsNil)
   202  }
   203  
   204  // patchDF100GB ensures that df always returns 100GB.
   205  func (s *InitialiserSuite) patchDF100GB() {
   206  	df100 := func(path string) (uint64, error) {
   207  		return 100 * 1024 * 1024 * 1024, nil
   208  	}
   209  	s.PatchValue(&df, df100)
   210  }
   211  
   212  func (s *InitialiserSuite) TestConfigureProxies(c *gc.C) {
   213  	ctrl := gomock.NewController(c)
   214  	cSvr := lxdtesting.NewMockContainerServer(ctrl)
   215  	s.PatchForProxyUpdate(c, cSvr, true)
   216  
   217  	updateReq := api.ServerPut{Config: map[string]interface{}{
   218  		"core.proxy_http":         "http://test.local/http/proxy",
   219  		"core.proxy_https":        "http://test.local/https/proxy",
   220  		"core.proxy_ignore_hosts": "test.local,localhost",
   221  	}}
   222  	gomock.InOrder(
   223  		cSvr.EXPECT().GetServer().Return(&api.Server{}, lxdtesting.ETag, nil).Times(2),
   224  		cSvr.EXPECT().UpdateServer(updateReq, lxdtesting.ETag).Return(nil),
   225  	)
   226  
   227  	err := ConfigureLXDProxies(proxy.Settings{
   228  		Http:    "http://test.local/http/proxy",
   229  		Https:   "http://test.local/https/proxy",
   230  		NoProxy: "test.local,localhost",
   231  	})
   232  	c.Assert(err, jc.ErrorIsNil)
   233  }
   234  
   235  func (s *InitialiserSuite) TestInitializeSetsProxies(c *gc.C) {
   236  	if runtime.GOOS == "windows" {
   237  		c.Skip("no lxd on windows")
   238  	}
   239  
   240  	PatchHostSeries(s, "")
   241  
   242  	ctrl := gomock.NewController(c)
   243  	defer ctrl.Finish()
   244  	cSvr := lxdtesting.NewMockContainerServer(ctrl)
   245  	s.PatchForProxyUpdate(c, cSvr, true)
   246  
   247  	s.PatchEnvironment("http_proxy", "http://test.local/http/proxy")
   248  	s.PatchEnvironment("https_proxy", "http://test.local/https/proxy")
   249  	s.PatchEnvironment("no_proxy", "test.local,localhost")
   250  
   251  	updateReq := api.ServerPut{Config: map[string]interface{}{
   252  		"core.proxy_http":         "http://test.local/http/proxy",
   253  		"core.proxy_https":        "http://test.local/https/proxy",
   254  		"core.proxy_ignore_hosts": "test.local,localhost",
   255  	}}
   256  	gomock.InOrder(
   257  		cSvr.EXPECT().GetServer().Return(&api.Server{}, lxdtesting.ETag, nil).Times(2),
   258  		cSvr.EXPECT().UpdateServer(updateReq, lxdtesting.ETag).Return(nil),
   259  	)
   260  
   261  	container := NewContainerInitialiser()
   262  	err := container.Initialise()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  }
   265  
   266  func (s *InitialiserSuite) TestConfigureProxiesLXDNotRunning(c *gc.C) {
   267  	ctrl := gomock.NewController(c)
   268  	cSvr := lxdtesting.NewMockContainerServer(ctrl)
   269  	s.PatchForProxyUpdate(c, cSvr, false)
   270  
   271  	// No expected calls.
   272  	err := ConfigureLXDProxies(proxy.Settings{
   273  		Http:    "http://test.local/http/proxy",
   274  		Https:   "http://test.local/https/proxy",
   275  		NoProxy: "test.local,localhost",
   276  	})
   277  	c.Assert(err, jc.ErrorIsNil)
   278  }
   279  
   280  func (s *InitialiserSuite) TestFindAvailableSubnetWithInterfaceAddrsError(c *gc.C) {
   281  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   282  		return nil, errors.New("boom!")
   283  	})
   284  	subnet, err := findNextAvailableIPv4Subnet()
   285  	c.Assert(err, gc.ErrorMatches, "cannot get network interface addresses: boom!")
   286  	c.Assert(subnet, gc.Equals, "")
   287  }
   288  
   289  type testFindSubnetAddr struct {
   290  	val string
   291  }
   292  
   293  func (a testFindSubnetAddr) Network() string {
   294  	return "ip+net"
   295  }
   296  
   297  func (a testFindSubnetAddr) String() string {
   298  	return a.val
   299  }
   300  
   301  func testAddresses(c *gc.C, networks ...string) ([]net.Addr, error) {
   302  	addrs := make([]net.Addr, 0)
   303  	for _, n := range networks {
   304  		_, _, err := net.ParseCIDR(n)
   305  		if err != nil {
   306  			return nil, err
   307  		}
   308  		c.Assert(err, gc.IsNil)
   309  		addrs = append(addrs, testFindSubnetAddr{n})
   310  	}
   311  	return addrs, nil
   312  }
   313  
   314  func (s *InitialiserSuite) TestFindAvailableSubnetWithNoAddresses(c *gc.C) {
   315  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   316  		return testAddresses(c)
   317  	})
   318  	subnet, err := findNextAvailableIPv4Subnet()
   319  	c.Assert(err, gc.IsNil)
   320  	c.Assert(subnet, gc.Equals, "4")
   321  }
   322  
   323  func (s *InitialiserSuite) TestFindAvailableSubnetWithIPv6Only(c *gc.C) {
   324  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   325  		return testAddresses(c, "fe80::aa8e:a275:7ae0:34af/64")
   326  	})
   327  	subnet, err := findNextAvailableIPv4Subnet()
   328  	c.Assert(err, gc.IsNil)
   329  	c.Assert(subnet, gc.Equals, "4")
   330  }
   331  
   332  func (s *InitialiserSuite) TestFindAvailableSubnetWithIPv4OnlyAndNo10xSubnet(c *gc.C) {
   333  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   334  		return testAddresses(c, "192.168.1.64/24")
   335  	})
   336  	subnet, err := findNextAvailableIPv4Subnet()
   337  	c.Assert(err, gc.IsNil)
   338  	c.Assert(subnet, gc.Equals, "4")
   339  }
   340  
   341  func (s *InitialiserSuite) TestFindAvailableSubnetWithInvalidCIDR(c *gc.C) {
   342  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   343  		return []net.Addr{
   344  			testFindSubnetAddr{"10.0.0.1"},
   345  			testFindSubnetAddr{"10.0.5.1/24"}}, nil
   346  	})
   347  	subnet, err := findNextAvailableIPv4Subnet()
   348  	c.Assert(err, gc.IsNil)
   349  	c.Assert(subnet, gc.Equals, "4")
   350  }
   351  
   352  func (s *InitialiserSuite) TestFindAvailableSubnetWithIPv4AndExisting10xNetwork(c *gc.C) {
   353  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   354  		return testAddresses(c, "192.168.1.64/24", "10.0.0.1/24")
   355  	})
   356  	subnet, err := findNextAvailableIPv4Subnet()
   357  	c.Assert(err, gc.IsNil)
   358  	c.Assert(subnet, gc.Equals, "4")
   359  }
   360  
   361  func (s *InitialiserSuite) TestFindAvailableSubnetWithExisting10xNetworks(c *gc.C) {
   362  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   363  		// Note that 10.0.4.0 is a /23, so that includes 10.0.4.0/24 and 10.0.5.0/24
   364  		// And the one for 10.0.7.0/23 is also a /23 so it includes 10.0.6.0/24 as well as 10.0.7.0/24
   365  		return testAddresses(c, "192.168.1.0/24", "10.0.4.1/23", "10.0.7.5/23",
   366  			"::1/128", "10.0.3.1/24", "fe80::aa8e:a275:7ae0:34af/64")
   367  	})
   368  	subnet, err := findNextAvailableIPv4Subnet()
   369  	c.Assert(err, gc.IsNil)
   370  	c.Assert(subnet, gc.Equals, "8")
   371  }
   372  
   373  func (s *InitialiserSuite) TestFindAvailableSubnetUpperBoundInUse(c *gc.C) {
   374  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   375  		return testAddresses(c, "10.0.255.1/24")
   376  	})
   377  	subnet, err := findNextAvailableIPv4Subnet()
   378  	c.Assert(err, gc.IsNil)
   379  	c.Assert(subnet, gc.Equals, "4")
   380  }
   381  
   382  func (s *InitialiserSuite) TestFindAvailableSubnetUpperBoundAndLowerBoundInUse(c *gc.C) {
   383  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   384  		return testAddresses(c, "10.0.255.1/24", "10.0.0.1/24")
   385  	})
   386  	subnet, err := findNextAvailableIPv4Subnet()
   387  	c.Assert(err, gc.IsNil)
   388  	c.Assert(subnet, gc.Equals, "4")
   389  }
   390  
   391  func (s *InitialiserSuite) TestFindAvailableSubnetWithFull10xSubnet(c *gc.C) {
   392  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   393  		addrs := make([]net.Addr, 256)
   394  		for i := 0; i < 256; i++ {
   395  			subnet := fmt.Sprintf("10.0.%v.1/24", i)
   396  			addrs[i] = testFindSubnetAddr{subnet}
   397  		}
   398  		return addrs, nil
   399  	})
   400  	subnet, err := findNextAvailableIPv4Subnet()
   401  	c.Assert(err, gc.ErrorMatches, "could not find unused subnet")
   402  	c.Assert(subnet, gc.Equals, "")
   403  }
   404  
   405  func (s *InitialiserSuite) TestParseLXDBridgeFileValues(c *gc.C) {
   406  	insignificantContent := `
   407  # Comment 1, followed by empty line.
   408  
   409  # Comment 2, followed by empty line.
   410  
   411    And a line that has content, but is not a comment, nor a key/value pair.
   412  `
   413  	for i, test := range []struct {
   414  		desc     string
   415  		content  string
   416  		expected map[string]string
   417  	}{{
   418  		desc:     "empty content",
   419  		content:  "",
   420  		expected: map[string]string{},
   421  	}, {
   422  		desc:     "only comments and empty lines",
   423  		content:  insignificantContent,
   424  		expected: map[string]string{},
   425  	}, {
   426  		desc:     "missing key",
   427  		content:  "=a",
   428  		expected: map[string]string{},
   429  	}, {
   430  		desc:    "empty value",
   431  		content: "a=",
   432  		expected: map[string]string{
   433  			"a": "",
   434  		},
   435  	}, {
   436  		desc:    "value defined, but empty",
   437  		content: `a=""`,
   438  		expected: map[string]string{
   439  			"a": "",
   440  		},
   441  	}, {
   442  		desc:    "multiple entries",
   443  		content: "a=b\nc=d\ne=f",
   444  		expected: map[string]string{
   445  			"a": "b",
   446  			"c": "d",
   447  			"e": "f",
   448  		},
   449  	}, {
   450  		desc:    "comment with leading whitespace",
   451  		content: " #a=b\nc=d\ne=f",
   452  		expected: map[string]string{
   453  			"c": "d",
   454  			"e": "f",
   455  		},
   456  	}, {
   457  		desc:    "key/value pairs with leading and trailing whitespace",
   458  		content: " a=b\n c=d \ne=f ",
   459  		expected: map[string]string{
   460  			"a": "b",
   461  			"c": "d",
   462  			"e": "f",
   463  		},
   464  	}} {
   465  		c.Logf("test #%d - %s", i, test.desc)
   466  		values := parseLXDBridgeConfigValues(test.content)
   467  		c.Check(values, gc.DeepEquals, test.expected)
   468  	}
   469  }
   470  
   471  func (s *InitialiserSuite) TestParseLXDBridgeFileValuesWithRealWorldContent(c *gc.C) {
   472  	expected := map[string]string{
   473  		"USE_LXD_BRIDGE":      "true",
   474  		"EXISTING_BRIDGE":     "",
   475  		"LXD_BRIDGE":          "lxdbr0",
   476  		"LXD_CONFILE":         "",
   477  		"LXD_DOMAIN":          "lxd",
   478  		"LXD_IPV4_ADDR":       "10.0.4.1",
   479  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   480  		"LXD_IPV4_NETWORK":    "10.0.4.1/24",
   481  		"LXD_IPV4_DHCP_RANGE": "10.0.4.2,10.0.4.100",
   482  		"LXD_IPV4_DHCP_MAX":   "50",
   483  		"LXD_IPV4_NAT":        "true",
   484  		"LXD_IPV6_ADDR":       "2001:470:b2b5:9999::1",
   485  		"LXD_IPV6_MASK":       "64",
   486  		"LXD_IPV6_NETWORK":    "2001:470:b2b5:9999::1/64",
   487  		"LXD_IPV6_NAT":        "true",
   488  		"LXD_IPV6_PROXY":      "true",
   489  	}
   490  	values := parseLXDBridgeConfigValues(lxdBridgeContent)
   491  	c.Check(values, gc.DeepEquals, expected)
   492  }
   493  
   494  func (s *InitialiserSuite) TestBridgeConfigurationWithNoChangeRequired(c *gc.C) {
   495  	result, err := bridgeConfiguration(lxdBridgeContent)
   496  	c.Assert(err, gc.IsNil)
   497  	c.Assert(lxdBridgeContent, gc.Equals, result)
   498  }
   499  
   500  func (s *InitialiserSuite) TestBridgeConfigurationWithInterfacesError(c *gc.C) {
   501  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   502  		return nil, errors.New("boom!")
   503  	})
   504  	result, err := bridgeConfiguration("")
   505  	c.Assert(err, gc.ErrorMatches, "cannot get network interface addresses: boom!")
   506  	c.Assert(result, gc.Equals, "")
   507  }
   508  
   509  func (s *InitialiserSuite) TestBridgeConfigurationWithNewSubnet(c *gc.C) {
   510  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   511  		return testAddresses(c, "10.0.2.1/24")
   512  	})
   513  
   514  	expectedValues := map[string]string{
   515  		"USE_LXD_BRIDGE":      "true",
   516  		"EXISTING_BRIDGE":     "",
   517  		"LXD_BRIDGE":          "lxdbr0",
   518  		"LXD_IPV4_ADDR":       "10.0.4.1",
   519  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   520  		"LXD_IPV4_NETWORK":    "10.0.4.1/24",
   521  		"LXD_IPV4_DHCP_RANGE": "10.0.4.2,10.0.4.254",
   522  		"LXD_IPV4_DHCP_MAX":   "253",
   523  		"LXD_IPV4_NAT":        "true",
   524  		"LXD_IPV6_PROXY":      "false",
   525  	}
   526  
   527  	result, err := bridgeConfiguration(`LXD_IPV4_ADDR=""`)
   528  	c.Assert(err, gc.IsNil)
   529  	actualValues := parseLXDBridgeConfigValues(result)
   530  	c.Assert(actualValues, gc.DeepEquals, expectedValues)
   531  }