github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/container/lxd/initialisation_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build go1.3
     5  
     6  package lxd
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"net"
    12  	"runtime"
    13  
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/utils/packaging/commands"
    17  	"github.com/juju/utils/packaging/manager"
    18  	"github.com/juju/utils/proxy"
    19  	"github.com/juju/utils/series"
    20  	gc "gopkg.in/check.v1"
    21  
    22  	coretesting "github.com/juju/juju/testing"
    23  )
    24  
    25  type InitialiserSuite struct {
    26  	coretesting.BaseSuite
    27  	calledCmds []string
    28  	testing.PatchExecHelper
    29  }
    30  
    31  var _ = gc.Suite(&InitialiserSuite{})
    32  
    33  const lxdBridgeContent = `# WARNING: Don't modify this file by hand, it is generated by debconf!
    34  # To update those values, please run "dpkg-reconfigure lxd"
    35  
    36  # Whether to setup a new bridge
    37  USE_LXD_BRIDGE="true"
    38  EXISTING_BRIDGE=""
    39  
    40  # Bridge name
    41  LXD_BRIDGE="lxdbr0"
    42  
    43  # dnsmasq configuration path
    44  LXD_CONFILE=""
    45  
    46  # dnsmasq domain
    47  LXD_DOMAIN="lxd"
    48  
    49  # IPv4
    50  LXD_IPV4_ADDR="10.0.4.1"
    51  LXD_IPV4_NETMASK="255.255.255.0"
    52  LXD_IPV4_NETWORK="10.0.4.1/24"
    53  LXD_IPV4_DHCP_RANGE="10.0.4.2,10.0.4.100"
    54  LXD_IPV4_DHCP_MAX="50"
    55  LXD_IPV4_NAT="true"
    56  
    57  # IPv6
    58  LXD_IPV6_ADDR="2001:470:b2b5:9999::1"
    59  LXD_IPV6_MASK="64"
    60  LXD_IPV6_NETWORK="2001:470:b2b5:9999::1/64"
    61  LXD_IPV6_NAT="true"
    62  
    63  # Proxy server
    64  LXD_IPV6_PROXY="true"
    65  `
    66  
    67  // getMockRunCommandWithRetry is a helper function which returns a function
    68  // with an identical signature to manager.RunCommandWithRetry which saves each
    69  // command it recieves in a slice and always returns no output, error code 0
    70  // and a nil error.
    71  func getMockRunCommandWithRetry(calledCmds *[]string) func(string, func(string) error) (string, int, error) {
    72  	return func(cmd string, fatalError func(string) error) (string, int, error) {
    73  		*calledCmds = append(*calledCmds, cmd)
    74  		return "", 0, nil
    75  	}
    76  }
    77  
    78  func (s *InitialiserSuite) SetUpTest(c *gc.C) {
    79  	s.BaseSuite.SetUpTest(c)
    80  	s.calledCmds = []string{}
    81  	s.PatchValue(&manager.RunCommandWithRetry, getMockRunCommandWithRetry(&s.calledCmds))
    82  	s.PatchValue(&configureZFS, func() {})
    83  	s.PatchValue(&configureLXDBridge, func() error { return nil })
    84  	s.PatchValue(&getLXDConfigSetter, func() (configSetter, error) {
    85  		return &mockConfigSetter{}, nil
    86  	})
    87  	// Fake the lxc executable for all the tests.
    88  	testing.PatchExecutableAsEchoArgs(c, s, "lxc")
    89  }
    90  
    91  func (s *InitialiserSuite) TestLTSSeriesPackages(c *gc.C) {
    92  	// Momentarily, the only series with a dedicated cloud archive is precise,
    93  	// which we will use for the following test:
    94  	paccmder, err := commands.NewPackageCommander("trusty")
    95  	c.Assert(err, jc.ErrorIsNil)
    96  
    97  	s.PatchValue(&series.HostSeries, func() string { return "trusty" })
    98  	container := NewContainerInitialiser("trusty")
    99  
   100  	err = container.Initialise()
   101  	c.Assert(err, jc.ErrorIsNil)
   102  
   103  	c.Assert(s.calledCmds, gc.DeepEquals, []string{
   104  		paccmder.InstallCmd("--target-release", "trusty-backports", "lxd"),
   105  	})
   106  }
   107  
   108  func (s *InitialiserSuite) TestNoSeriesPackages(c *gc.C) {
   109  	// Here we want to test for any other series whilst avoiding the
   110  	// possibility of hitting a cloud archive-requiring release.
   111  	// As such, we simply pass an empty series.
   112  	paccmder, err := commands.NewPackageCommander("xenial")
   113  	c.Assert(err, jc.ErrorIsNil)
   114  
   115  	container := NewContainerInitialiser("")
   116  
   117  	err = container.Initialise()
   118  	c.Assert(err, jc.ErrorIsNil)
   119  
   120  	c.Assert(s.calledCmds, gc.DeepEquals, []string{
   121  		paccmder.InstallCmd("lxd"),
   122  	})
   123  }
   124  
   125  type mockConfigSetter struct {
   126  	keys   []string
   127  	values []string
   128  }
   129  
   130  func (m *mockConfigSetter) SetConfig(key, value string) error {
   131  	m.keys = append(m.keys, key)
   132  	m.values = append(m.values, value)
   133  	return nil
   134  }
   135  
   136  func (s *InitialiserSuite) TestConfigureProxies(c *gc.C) {
   137  	// This test is safe on windows because it mocks out all lxd moving parts.
   138  	setter := &mockConfigSetter{}
   139  	s.PatchValue(&getLXDConfigSetter, func() (configSetter, error) {
   140  		return setter, nil
   141  	})
   142  
   143  	proxies := proxy.Settings{
   144  		Http:    "http://test.local/http/proxy",
   145  		Https:   "http://test.local/https/proxy",
   146  		NoProxy: "test.local,localhost",
   147  	}
   148  	err := ConfigureLXDProxies(proxies)
   149  	c.Assert(err, jc.ErrorIsNil)
   150  
   151  	c.Check(setter.keys, jc.DeepEquals, []string{
   152  		"core.proxy_http", "core.proxy_https", "core.proxy_ignore_hosts",
   153  	})
   154  	c.Check(setter.values, jc.DeepEquals, []string{
   155  		"http://test.local/http/proxy", "http://test.local/https/proxy", "test.local,localhost",
   156  	})
   157  }
   158  
   159  func (s *InitialiserSuite) TestInitializeSetsProxies(c *gc.C) {
   160  	if runtime.GOOS == "windows" {
   161  		c.Skip("no lxd on windows")
   162  	}
   163  
   164  	setter := &mockConfigSetter{}
   165  	s.PatchValue(&getLXDConfigSetter, func() (configSetter, error) {
   166  		return setter, nil
   167  	})
   168  
   169  	s.PatchEnvironment("http_proxy", "http://test.local/http/proxy")
   170  	s.PatchEnvironment("https_proxy", "http://test.local/https/proxy")
   171  	s.PatchEnvironment("no_proxy", "test.local,localhost")
   172  
   173  	container := NewContainerInitialiser("")
   174  	err := container.Initialise()
   175  	c.Assert(err, jc.ErrorIsNil)
   176  
   177  	c.Check(setter.keys, jc.DeepEquals, []string{
   178  		"core.proxy_http", "core.proxy_https", "core.proxy_ignore_hosts",
   179  	})
   180  	c.Check(setter.values, jc.DeepEquals, []string{
   181  		"http://test.local/http/proxy", "http://test.local/https/proxy", "test.local,localhost",
   182  	})
   183  }
   184  
   185  func (s *InitialiserSuite) TestFindAvailableSubnetWithInterfaceAddrsError(c *gc.C) {
   186  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   187  		return nil, errors.New("boom!")
   188  	})
   189  	subnet, err := findNextAvailableIPv4Subnet()
   190  	c.Assert(err, gc.ErrorMatches, "cannot get network interface addresses: boom!")
   191  	c.Assert(subnet, gc.Equals, "")
   192  }
   193  
   194  type testFindSubnetAddr struct {
   195  	val string
   196  }
   197  
   198  func (a testFindSubnetAddr) Network() string {
   199  	return "ip+net"
   200  }
   201  
   202  func (a testFindSubnetAddr) String() string {
   203  	return a.val
   204  }
   205  
   206  func testAddresses(c *gc.C, networks ...string) ([]net.Addr, error) {
   207  	addrs := make([]net.Addr, 0)
   208  	for _, n := range networks {
   209  		_, _, err := net.ParseCIDR(n)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		c.Assert(err, gc.IsNil)
   214  		addrs = append(addrs, testFindSubnetAddr{n})
   215  	}
   216  	return addrs, nil
   217  }
   218  
   219  func (s *InitialiserSuite) TestFindAvailableSubnetWithNoAddresses(c *gc.C) {
   220  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   221  		return testAddresses(c)
   222  	})
   223  	subnet, err := findNextAvailableIPv4Subnet()
   224  	c.Assert(err, gc.IsNil)
   225  	c.Assert(subnet, gc.Equals, "0")
   226  }
   227  
   228  func (s *InitialiserSuite) TestFindAvailableSubnetWithIPv6Only(c *gc.C) {
   229  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   230  		return testAddresses(c, "fe80::aa8e:a275:7ae0:34af/64")
   231  	})
   232  	subnet, err := findNextAvailableIPv4Subnet()
   233  	c.Assert(err, gc.IsNil)
   234  	c.Assert(subnet, gc.Equals, "0")
   235  }
   236  
   237  func (s *InitialiserSuite) TestFindAvailableSubnetWithIPv4OnlyAndNo10xSubnet(c *gc.C) {
   238  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   239  		return testAddresses(c, "192.168.1.64/24")
   240  	})
   241  	subnet, err := findNextAvailableIPv4Subnet()
   242  	c.Assert(err, gc.IsNil)
   243  	c.Assert(subnet, gc.Equals, "0")
   244  }
   245  
   246  func (s *InitialiserSuite) TestFindAvailableSubnetWithInvalidCIDR(c *gc.C) {
   247  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   248  		return []net.Addr{
   249  			testFindSubnetAddr{"10.0.0.1"},
   250  			testFindSubnetAddr{"10.0.5.1/24"}}, nil
   251  	})
   252  	subnet, err := findNextAvailableIPv4Subnet()
   253  	c.Assert(err, gc.IsNil)
   254  	c.Assert(subnet, gc.Equals, "6")
   255  }
   256  
   257  func (s *InitialiserSuite) TestFindAvailableSubnetWithIPv4AndExisting10xNetwork(c *gc.C) {
   258  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   259  		return testAddresses(c, "192.168.1.64/24", "10.0.0.1/24")
   260  	})
   261  	subnet, err := findNextAvailableIPv4Subnet()
   262  	c.Assert(err, gc.IsNil)
   263  	c.Assert(subnet, gc.Equals, "1")
   264  }
   265  
   266  func (s *InitialiserSuite) TestFindAvailableSubnetWithExisting10xNetworks(c *gc.C) {
   267  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   268  		return testAddresses(c, "192.168.1.0/24", "10.0.4.1/24", "::1/128", "10.0.3.1/24", "fe80::aa8e:a275:7ae0:34af/64")
   269  	})
   270  	subnet, err := findNextAvailableIPv4Subnet()
   271  	c.Assert(err, gc.IsNil)
   272  	c.Assert(subnet, gc.Equals, "5")
   273  }
   274  
   275  func (s *InitialiserSuite) TestFindAvailableSubnetUpperBoundInUse(c *gc.C) {
   276  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   277  		return testAddresses(c, "10.0.255.1/24")
   278  	})
   279  	subnet, err := findNextAvailableIPv4Subnet()
   280  	c.Assert(err, gc.IsNil)
   281  	c.Assert(subnet, gc.Equals, "0")
   282  }
   283  
   284  func (s *InitialiserSuite) TestFindAvailableSubnetUpperBoundAndLowerBoundInUse(c *gc.C) {
   285  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   286  		return testAddresses(c, "10.0.255.1/24", "10.0.0.1/24")
   287  	})
   288  	subnet, err := findNextAvailableIPv4Subnet()
   289  	c.Assert(err, gc.IsNil)
   290  	c.Assert(subnet, gc.Equals, "1")
   291  }
   292  
   293  func (s *InitialiserSuite) TestFindAvailableSubnetWithFull10xSubnet(c *gc.C) {
   294  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   295  		addrs := make([]net.Addr, 256)
   296  		for i := 0; i < 256; i++ {
   297  			subnet := fmt.Sprintf("10.0.%v.1/24", i)
   298  			addrs[i] = testFindSubnetAddr{subnet}
   299  		}
   300  		return addrs, nil
   301  	})
   302  	subnet, err := findNextAvailableIPv4Subnet()
   303  	c.Assert(err, gc.ErrorMatches, "could not find unused subnet")
   304  	c.Assert(subnet, gc.Equals, "")
   305  }
   306  
   307  func (s *InitialiserSuite) TestParseLXDBridgeFileValues(c *gc.C) {
   308  	insignificantContent := `
   309  # Comment 1, followed by empty line.
   310  
   311  # Comment 2, followed by empty line.
   312  
   313    And a line that has content, but is not a comment, nor a key/value pair.
   314  `
   315  	for i, test := range []struct {
   316  		desc     string
   317  		content  string
   318  		expected map[string]string
   319  	}{{
   320  		desc:     "empty content",
   321  		content:  "",
   322  		expected: map[string]string{},
   323  	}, {
   324  		desc:     "only comments and empty lines",
   325  		content:  insignificantContent,
   326  		expected: map[string]string{},
   327  	}, {
   328  		desc:     "missing key",
   329  		content:  "=a",
   330  		expected: map[string]string{},
   331  	}, {
   332  		desc:    "empty value",
   333  		content: "a=",
   334  		expected: map[string]string{
   335  			"a": "",
   336  		},
   337  	}, {
   338  		desc:    "value defined, but empty",
   339  		content: `a=""`,
   340  		expected: map[string]string{
   341  			"a": "",
   342  		},
   343  	}, {
   344  		desc:    "multiple entries",
   345  		content: "a=b\nc=d\ne=f",
   346  		expected: map[string]string{
   347  			"a": "b",
   348  			"c": "d",
   349  			"e": "f",
   350  		},
   351  	}, {
   352  		desc:    "comment with leading whitespace",
   353  		content: " #a=b\nc=d\ne=f",
   354  		expected: map[string]string{
   355  			"c": "d",
   356  			"e": "f",
   357  		},
   358  	}, {
   359  		desc:    "key/value pairs with leading and trailing whitespace",
   360  		content: " a=b\n c=d \ne=f ",
   361  		expected: map[string]string{
   362  			"a": "b",
   363  			"c": "d",
   364  			"e": "f",
   365  		},
   366  	}} {
   367  		c.Logf("test #%d - %s", i, test.desc)
   368  		values := parseLXDBridgeConfigValues(test.content)
   369  		c.Check(values, gc.DeepEquals, test.expected)
   370  	}
   371  }
   372  
   373  func (s *InitialiserSuite) TestParseLXDBridgeFileValuesWithRealWorldContent(c *gc.C) {
   374  	expected := map[string]string{
   375  		"USE_LXD_BRIDGE":      "true",
   376  		"EXISTING_BRIDGE":     "",
   377  		"LXD_BRIDGE":          "lxdbr0",
   378  		"LXD_CONFILE":         "",
   379  		"LXD_DOMAIN":          "lxd",
   380  		"LXD_IPV4_ADDR":       "10.0.4.1",
   381  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   382  		"LXD_IPV4_NETWORK":    "10.0.4.1/24",
   383  		"LXD_IPV4_DHCP_RANGE": "10.0.4.2,10.0.4.100",
   384  		"LXD_IPV4_DHCP_MAX":   "50",
   385  		"LXD_IPV4_NAT":        "true",
   386  		"LXD_IPV6_ADDR":       "2001:470:b2b5:9999::1",
   387  		"LXD_IPV6_MASK":       "64",
   388  		"LXD_IPV6_NETWORK":    "2001:470:b2b5:9999::1/64",
   389  		"LXD_IPV6_NAT":        "true",
   390  		"LXD_IPV6_PROXY":      "true",
   391  	}
   392  	values := parseLXDBridgeConfigValues(lxdBridgeContent)
   393  	c.Check(values, gc.DeepEquals, expected)
   394  }
   395  
   396  func (s *InitialiserSuite) TestBridgeConfigurationWithNoChangeRequired(c *gc.C) {
   397  	result, err := bridgeConfiguration(lxdBridgeContent)
   398  	c.Assert(err, gc.IsNil)
   399  	c.Assert(lxdBridgeContent, gc.Equals, result)
   400  }
   401  
   402  func (s *InitialiserSuite) TestBridgeConfigurationWithInterfacesError(c *gc.C) {
   403  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   404  		return nil, errors.New("boom!")
   405  	})
   406  	result, err := bridgeConfiguration("")
   407  	c.Assert(err, gc.ErrorMatches, "cannot get network interface addresses: boom!")
   408  	c.Assert(result, gc.Equals, "")
   409  }
   410  
   411  func (s *InitialiserSuite) TestBridgeConfigurationWithNewSubnet(c *gc.C) {
   412  	s.PatchValue(&interfaceAddrs, func() ([]net.Addr, error) {
   413  		return testAddresses(c, "10.0.4.1/24")
   414  	})
   415  
   416  	expectedValues := map[string]string{
   417  		"USE_LXD_BRIDGE":      "true",
   418  		"EXISTING_BRIDGE":     "",
   419  		"LXD_BRIDGE":          "lxdbr0",
   420  		"LXD_IPV4_ADDR":       "10.0.5.1",
   421  		"LXD_IPV4_NETMASK":    "255.255.255.0",
   422  		"LXD_IPV4_NETWORK":    "10.0.5.1/24",
   423  		"LXD_IPV4_DHCP_RANGE": "10.0.5.2,10.0.5.254",
   424  		"LXD_IPV4_DHCP_MAX":   "253",
   425  		"LXD_IPV4_NAT":        "true",
   426  		"LXD_IPV6_PROXY":      "false",
   427  	}
   428  
   429  	result, err := bridgeConfiguration(`LXD_IPV4_ADDR=""`)
   430  	c.Assert(err, gc.IsNil)
   431  	actualValues := parseLXDBridgeConfigValues(result)
   432  	c.Assert(expectedValues, gc.DeepEquals, actualValues)
   433  }