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 }