github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/container/lxc/lxc_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxc_test 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "runtime" 12 "strings" 13 stdtesting "testing" 14 "time" 15 16 "github.com/juju/errors" 17 "github.com/juju/loggo" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils/proxy" 20 "github.com/juju/utils/set" 21 "github.com/juju/utils/symlink" 22 gc "gopkg.in/check.v1" 23 goyaml "gopkg.in/yaml.v2" 24 "launchpad.net/golxc" 25 26 "github.com/juju/juju/agent" 27 "github.com/juju/juju/container" 28 "github.com/juju/juju/container/lxc" 29 "github.com/juju/juju/container/lxc/mock" 30 lxctesting "github.com/juju/juju/container/lxc/testing" 31 containertesting "github.com/juju/juju/container/testing" 32 "github.com/juju/juju/environs/config" 33 "github.com/juju/juju/feature" 34 "github.com/juju/juju/instance" 35 instancetest "github.com/juju/juju/instance/testing" 36 "github.com/juju/juju/network" 37 "github.com/juju/juju/provider/dummy" 38 "github.com/juju/juju/status" 39 coretesting "github.com/juju/juju/testing" 40 ) 41 42 func Test(t *stdtesting.T) { 43 if runtime.GOOS == "windows" { 44 t.Skip("LXC is currently not supported on windows") 45 } 46 gc.TestingT(t) 47 } 48 49 type LxcSuite struct { 50 lxctesting.TestSuite 51 52 events chan mock.Event 53 useClone bool 54 useAUFS bool 55 logDir string 56 loopDeviceManager mockLoopDeviceManager 57 } 58 59 var _ = gc.Suite(&LxcSuite{}) 60 61 var lxcCgroupContents = `11:hugetlb:/lxc/juju-machine-1-lxc-0 62 10:perf_event:/lxc/juju-machine-1-lxc-0 63 9:blkio:/lxc/juju-machine-1-lxc-0 64 8:freezer:/lxc/juju-machine-1-lxc-0 65 7:devices:/lxc/juju-machine-1-lxc-0 66 6:memory:/lxc/juju-machine-1-lxc-0 67 5:cpuacct:/lxc/juju-machine-1-lxc-0 68 4:cpu:/lxc/juju-machine-1-lxc-0 69 3:cpuset:/lxc/juju-machine-1-lxc-0 70 2:name=systemd:/lxc/juju-machine-1-lxc-0 71 ` 72 73 var hostCgroupContents = `11:hugetlb:/ 74 10:perf_event:/ 75 9:blkio:/ 76 8:freezer:/ 77 7:devices:/ 78 6:memory:/ 79 5:cpuacct:/ 80 4:cpu:/ 81 3:cpuset:/ 82 2:name=systemd:/ 83 ` 84 85 var malformedCgroupFile = `some bogus content 86 more bogus content` 87 88 func (s *LxcSuite) SetUpTest(c *gc.C) { 89 s.TestSuite.SetUpTest(c) 90 s.SetFeatureFlags(feature.AddressAllocation) 91 s.logDir = c.MkDir() 92 loggo.GetLogger("juju.container.lxc").SetLogLevel(loggo.TRACE) 93 s.events = make(chan mock.Event, 25) 94 s.TestSuite.ContainerFactory.AddListener(s.events) 95 s.PatchValue(&lxc.TemplateLockDir, c.MkDir()) 96 s.PatchValue(&lxc.TemplateStopTimeout, 500*time.Millisecond) 97 s.loopDeviceManager = mockLoopDeviceManager{} 98 s.AddCleanup(func(*gc.C) { 99 s.TestSuite.ContainerFactory.RemoveListener(s.events) 100 close(s.events) 101 }) 102 } 103 104 func (t *LxcSuite) TestPreferFastLXC(c *gc.C) { 105 for i, test := range []struct { 106 message string 107 releaseVersion string 108 expected bool 109 }{{ 110 message: "missing release file", 111 }, { 112 message: "precise release", 113 releaseVersion: "12.04", 114 }, { 115 message: "trusty release", 116 releaseVersion: "14.04", 117 expected: true, 118 }, { 119 message: "unstable unicorn", 120 releaseVersion: "14.10", 121 expected: true, 122 }, { 123 message: "lucid", 124 releaseVersion: "10.04", 125 }} { 126 c.Logf("%v: %v", i, test.message) 127 value := lxc.PreferFastLXC(test.releaseVersion) 128 c.Assert(value, gc.Equals, test.expected) 129 } 130 } 131 132 func (s *LxcSuite) TestContainerManagerLXCClone(c *gc.C) { 133 type test struct { 134 releaseVersion string 135 useClone string 136 expectClone bool 137 } 138 tests := []test{{ 139 releaseVersion: "12.04", 140 useClone: "true", 141 expectClone: true, 142 }, { 143 releaseVersion: "14.04", 144 expectClone: true, 145 }, { 146 releaseVersion: "12.04", 147 useClone: "false", 148 }, { 149 releaseVersion: "14.04", 150 useClone: "false", 151 }} 152 153 for i, test := range tests { 154 c.Logf("test %d: %v", i, test) 155 s.PatchValue(lxc.ReleaseVersion, func() string { return test.releaseVersion }) 156 157 mgr, err := lxc.NewContainerManager(container.ManagerConfig{ 158 container.ConfigName: "juju", 159 "use-clone": test.useClone, 160 }, &containertesting.MockURLGetter{}) 161 c.Assert(err, jc.ErrorIsNil) 162 c.Check(lxc.GetCreateWithCloneValue(mgr), gc.Equals, test.expectClone) 163 } 164 } 165 166 func (s *LxcSuite) TestContainerDirFilesystem(c *gc.C) { 167 for i, test := range []struct { 168 message string 169 output string 170 expected string 171 errorMatch string 172 }{{ 173 message: "btrfs", 174 output: "Type\nbtrfs\n", 175 expected: lxc.Btrfs, 176 }, { 177 message: "ext4", 178 output: "Type\next4\n", 179 expected: "ext4", 180 }, { 181 message: "not enough output", 182 output: "foo", 183 errorMatch: "could not determine filesystem type", 184 }} { 185 c.Logf("%v: %s", i, test.message) 186 s.HookCommandOutput(&lxc.FsCommandOutput, []byte(test.output), nil) 187 value, err := lxc.ContainerDirFilesystem() 188 if test.errorMatch == "" { 189 c.Check(err, jc.ErrorIsNil) 190 c.Check(value, gc.Equals, test.expected) 191 } else { 192 c.Check(err, gc.ErrorMatches, test.errorMatch) 193 } 194 } 195 } 196 197 func (*LxcSuite) TestParseConfigLine(c *gc.C) { 198 for i, test := range []struct { 199 about string 200 input string 201 setting string 202 value string 203 }{{ 204 about: "empty line", 205 input: "", 206 setting: "", 207 value: "", 208 }, { 209 about: "line with spaces", 210 input: " line with spaces ", 211 setting: "", 212 value: "", 213 }, { 214 about: "comments", 215 input: "# comment", 216 setting: "", 217 value: "", 218 }, { 219 about: "commented setting", 220 input: "#lxc.flag = disabled", 221 setting: "", 222 value: "", 223 }, { 224 about: "comments with spaces", 225 input: " # comment with spaces ", 226 setting: "", 227 value: "", 228 }, { 229 about: "not a setting", 230 input: "anything here", 231 setting: "", 232 value: "", 233 }, { 234 about: "valid setting, no whitespace", 235 input: "lxc.setting=value", 236 setting: "lxc.setting", 237 value: "value", 238 }, { 239 about: "valid setting, with whitespace", 240 input: " lxc.setting = value ", 241 setting: "lxc.setting", 242 value: "value", 243 }, { 244 about: "valid setting, with comment on the value", 245 input: "lxc.setting = value # comment # foo ", 246 setting: "lxc.setting", 247 value: "value", 248 }, { 249 about: "valid setting, with comment, spaces and extra equals", 250 input: "lxc.my.best.setting = foo=bar, but # not really ", 251 setting: "lxc.my.best.setting", 252 value: "foo=bar, but", 253 }} { 254 c.Logf("test %d: %s", i, test.about) 255 setting, value := lxc.ParseConfigLine(test.input) 256 c.Check(setting, gc.Equals, test.setting) 257 c.Check(value, gc.Equals, test.value) 258 if setting == "" { 259 c.Check(value, gc.Equals, "") 260 } 261 } 262 } 263 264 func (s *LxcSuite) TestUpdateContainerConfig(c *gc.C) { 265 networkConfig := container.BridgeNetworkConfig("nic42", 4321, []network.InterfaceInfo{{ 266 DeviceIndex: 0, 267 CIDR: "0.1.2.0/20", 268 InterfaceName: "eth0", 269 MACAddress: "aa:bb:cc:dd:ee:f0", 270 Address: network.NewAddress("0.1.2.3"), 271 GatewayAddress: network.NewAddress("0.1.2.1"), 272 }, { 273 DeviceIndex: 1, 274 InterfaceName: "eth1", 275 }}) 276 storageConfig := &container.StorageConfig{ 277 AllowMount: true, 278 } 279 280 manager := s.makeManager(c, "test") 281 instanceConfig, err := containertesting.MockMachineConfig("1/lxc/0") 282 c.Assert(err, jc.ErrorIsNil) 283 envConfig, err := config.New(config.NoDefaults, dummy.SampleConfig()) 284 c.Assert(err, jc.ErrorIsNil) 285 instanceConfig.Config = envConfig 286 instance := containertesting.CreateContainerWithMachineAndNetworkAndStorageConfig( 287 c, manager, instanceConfig, networkConfig, storageConfig, 288 ) 289 name := string(instance.Id()) 290 291 // Append a few extra lines to the config. 292 extraLines := []string{ 293 " lxc.rootfs = /some/thing # else ", 294 "", 295 " # just comment", 296 "lxc.network.vlan.id=42", 297 "something else # ignore", 298 "lxc.network.type=veth", 299 "lxc.network.link = foo # comment", 300 "lxc.network.hwaddr = bar", 301 } 302 configPath := lxc.ContainerConfigFilename(name) 303 configFile, err := os.OpenFile(configPath, os.O_RDWR|os.O_APPEND, 0644) 304 c.Assert(err, jc.ErrorIsNil) 305 _, err = configFile.WriteString(strings.Join(extraLines, "\n") + "\n") 306 c.Assert(err, jc.ErrorIsNil) 307 err = configFile.Close() 308 c.Assert(err, jc.ErrorIsNil) 309 310 expectedConf := fmt.Sprintf(` 311 # network config 312 # interface "eth0" 313 lxc.network.type = veth 314 lxc.network.link = nic42 315 lxc.network.flags = up 316 lxc.network.name = eth0 317 lxc.network.hwaddr = aa:bb:cc:dd:ee:f0 318 lxc.network.ipv4 = 0.1.2.3/20 319 lxc.network.ipv4.gateway = 0.1.2.1 320 321 # interface "eth1" 322 lxc.network.type = veth 323 lxc.network.link = nic42 324 lxc.network.flags = up 325 lxc.network.name = eth1 326 327 328 lxc.mount.entry = %s var/log/juju none defaults,bind 0 0 329 330 lxc.aa_profile = lxc-container-default-with-mounting 331 lxc.cgroup.devices.allow = b 7:* rwm 332 lxc.cgroup.devices.allow = c 10:237 rwm 333 `, s.logDir) + strings.Join(extraLines, "\n") + "\n" 334 335 lxcConfContents, err := ioutil.ReadFile(configPath) 336 c.Assert(err, jc.ErrorIsNil) 337 c.Assert(string(lxcConfContents), gc.Equals, expectedConf) 338 339 linesToReplace := []string{ 340 "", // empty lines are ignored 341 " lxc.network.type = bar # free drinks !! ", // formatting is sanitized. 342 " # comments are ignored", 343 "lxc.network.type=foo", // replace the second "type". 344 "lxc.network.name = em0 # renamed now", // replace the first "name" 345 "lxc.network.name = em1", // replace the second "name" 346 "lxc.network.mtu = 1234", // replace only the first "mtu". 347 "lxc.network.hwaddr = ff:ee:dd:cc:bb:aa", // replace the first "hwaddr". 348 "lxc.network.hwaddr=deadbeef", // replace second "hwaddr". 349 "lxc.network.hwaddr=nonsense", // no third "hwaddr", so append. 350 "lxc.network.hwaddr = ", // no fourth "hwaddr" to remove - ignored. 351 "lxc.network.link=", // remove only the first "link" 352 "lxc.network.vlan.id=69", // replace. 353 "lxc.missing = appended", // missing - appended. 354 "lxc.network.type = phys", // replace the third "type". 355 "lxc.mount.entry=", // delete existing "entry". 356 "lxc.rootfs = /foo/bar", // replace first "rootfs". 357 "lxc.rootfs = /bar/foo", // append new. 358 } 359 newConfig := strings.Join(linesToReplace, "\n") 360 updatedConfig := ` 361 # network config 362 # interface "eth0" 363 lxc.network.type = bar 364 lxc.network.flags = up 365 lxc.network.name = em0 366 lxc.network.hwaddr = ff:ee:dd:cc:bb:aa 367 lxc.network.ipv4 = 0.1.2.3/20 368 lxc.network.ipv4.gateway = 0.1.2.1 369 370 # interface "eth1" 371 lxc.network.type = foo 372 lxc.network.link = nic42 373 lxc.network.flags = up 374 lxc.network.name = em1 375 376 377 378 lxc.aa_profile = lxc-container-default-with-mounting 379 lxc.cgroup.devices.allow = b 7:* rwm 380 lxc.cgroup.devices.allow = c 10:237 rwm 381 lxc.rootfs = /foo/bar 382 383 # just comment 384 lxc.network.vlan.id = 69 385 something else # ignore 386 lxc.network.type = phys 387 lxc.network.link = foo # comment 388 lxc.network.hwaddr = deadbeef 389 lxc.network.mtu = 1234 390 lxc.network.hwaddr = nonsense 391 lxc.missing = appended 392 lxc.rootfs = /bar/foo 393 ` 394 err = lxc.UpdateContainerConfig(name, newConfig) 395 c.Assert(err, jc.ErrorIsNil) 396 lxcConfContents, err = ioutil.ReadFile(configPath) 397 c.Assert(err, jc.ErrorIsNil) 398 c.Assert(string(lxcConfContents), gc.Equals, updatedConfig) 399 400 // Now test the example in updateContainerConfig's doc string. 401 oldConfig := ` 402 lxc.foo = off 403 404 lxc.bar=42 405 ` 406 newConfig = ` 407 lxc.bar= 408 lxc.foo = bar 409 lxc.foo = baz # xx 410 ` 411 updatedConfig = ` 412 lxc.foo = bar 413 414 lxc.foo = baz 415 ` 416 err = ioutil.WriteFile(configPath, []byte(oldConfig), 0644) 417 c.Assert(err, jc.ErrorIsNil) 418 err = lxc.UpdateContainerConfig(name, newConfig) 419 c.Assert(err, jc.ErrorIsNil) 420 lxcConfContents, err = ioutil.ReadFile(configPath) 421 c.Assert(err, jc.ErrorIsNil) 422 c.Assert(string(lxcConfContents), gc.Equals, updatedConfig) 423 } 424 425 func (*LxcSuite) TestReorderNetworkConfig(c *gc.C) { 426 path := c.MkDir() 427 configFile := filepath.Join(path, "config") 428 for i, test := range []struct { 429 about string 430 input string 431 shouldReorder bool 432 expectErr string 433 output string 434 }{{ 435 about: "empty input", 436 input: "", 437 shouldReorder: false, 438 expectErr: "", 439 output: "", 440 }, { 441 about: "no network settings", 442 input: ` 443 # comment 444 lxc.foo = bar 445 446 lxc.test=# none 447 lxc.bar.foo=42 # comment 448 `, 449 shouldReorder: false, 450 }, { 451 about: "just one lxc.network.type", 452 input: ` 453 # comment 454 lxc.foo = bar 455 456 lxc.network.type = veth 457 458 lxc.test=# none 459 lxc.bar.foo=42 # comment 460 `, 461 shouldReorder: false, 462 }, { 463 about: "correctly ordered network config", 464 input: ` 465 # Network configuration 466 lxc.network.type = veth 467 lxc.network.hwaddr = aa:bb:cc:dd:ee:f0 468 lxc.network.flags = up 469 lxc.network.link = br0 470 lxc.network.type = veth 471 lxc.network.flags = up 472 lxc.network.link = br2 473 lxc.network.hwaddr = aa:bb:cc:dd:ee:f1 474 lxc.network.name = eth1 475 lxc.network.type = veth 476 lxc.network.flags = up 477 lxc.network.link = br3 478 lxc.network.hwaddr = aa:bb:cc:dd:ee:f2 479 lxc.network.name = eth2 480 lxc.hook.mount = /usr/share/lxc/config/hook.sh 481 `, 482 shouldReorder: false, 483 }, { 484 about: "1 hwaddr before first type", 485 input: ` 486 lxc.foo = bar # stays here 487 # Network configuration 488 lxc.network.hwaddr = aa:bb:cc:dd:ee:f0 # comment 489 lxc.network.type = veth # comment2 490 lxc.network.flags = up # all the rest.. 491 lxc.network.link = br0 # ..is kept... 492 lxc.network.type = veth # ..as it is. 493 lxc.network.flags = up 494 lxc.network.link = br2 495 lxc.hook.mount = /usr/share/lxc/config/hook.sh 496 `, 497 shouldReorder: true, 498 output: ` 499 lxc.foo = bar # stays here 500 # Network configuration 501 lxc.network.type = veth # comment2 502 lxc.network.hwaddr = aa:bb:cc:dd:ee:f0 # comment 503 lxc.network.flags = up # all the rest.. 504 lxc.network.link = br0 # ..is kept... 505 lxc.network.type = veth # ..as it is. 506 lxc.network.flags = up 507 lxc.network.link = br2 508 lxc.hook.mount = /usr/share/lxc/config/hook.sh 509 `, 510 }, { 511 about: "several network lines before first type", 512 input: ` 513 lxc.foo = bar # stays here 514 # Network configuration 515 lxc.network.hwaddr = aa:bb:cc:dd:ee:f0 # comment 516 lxc.network.flags = up # first up 517 lxc.network.link = br0 518 lxc.network.type = veth # comment2 519 lxc.network.type = vlan 520 lxc.network.flags = up # all the rest.. 521 lxc.network.link = br1 # ...is kept... 522 lxc.network.vlan.id = 42 # ...as it is. 523 lxc.hook.mount = /usr/share/lxc/config/hook.sh 524 `, 525 shouldReorder: true, 526 output: ` 527 lxc.foo = bar # stays here 528 # Network configuration 529 lxc.network.type = veth # comment2 530 lxc.network.hwaddr = aa:bb:cc:dd:ee:f0 # comment 531 lxc.network.flags = up # first up 532 lxc.network.link = br0 533 lxc.network.type = vlan 534 lxc.network.flags = up # all the rest.. 535 lxc.network.link = br1 # ...is kept... 536 lxc.network.vlan.id = 42 # ...as it is. 537 lxc.hook.mount = /usr/share/lxc/config/hook.sh 538 `, 539 }, { 540 about: "one network setting without lxc.network.type", 541 input: ` 542 # comment 543 lxc.foo = bar 544 545 lxc.network.anything=goes#badly 546 547 lxc.test=# none 548 lxc.bar.foo=42 # comment 549 `, 550 expectErr: `cannot have line\(s\) ".*" without lxc.network.type in config ".*"`, 551 }, { 552 about: "several network settings without lxc.network.type", 553 input: ` 554 # comment 555 lxc.foo = bar 556 557 lxc.network.anything=goes#badly 558 lxc.network.vlan.id = 42 559 lxc.network.name = foo 560 561 lxc.test=# none 562 lxc.bar.foo=42 # comment 563 `, 564 expectErr: `cannot have line\(s\) ".*" without lxc.network.type in config ".*"`, 565 }} { 566 c.Logf("test %d: %q", i, test.about) 567 err := ioutil.WriteFile(configFile, []byte(test.input), 0644) 568 c.Assert(err, jc.ErrorIsNil) 569 wasReordered, err := lxc.ReorderNetworkConfig(configFile) 570 if test.expectErr != "" { 571 c.Check(err, gc.ErrorMatches, test.expectErr) 572 c.Check(wasReordered, gc.Equals, test.shouldReorder) 573 continue 574 } 575 data, err := ioutil.ReadFile(configFile) 576 c.Assert(err, jc.ErrorIsNil) 577 if test.shouldReorder { 578 c.Check(string(data), gc.Equals, test.output) 579 c.Check(wasReordered, jc.IsTrue) 580 } else { 581 c.Check(string(data), gc.Equals, test.input) 582 c.Check(wasReordered, jc.IsFalse) 583 } 584 } 585 } 586 587 func (s *LxcSuite) makeManager(c *gc.C, name string) container.Manager { 588 params := container.ManagerConfig{ 589 container.ConfigName: name, 590 } 591 // Need to ensure use-clone is explicitly set to avoid it 592 // being set based on the OS version. 593 params["use-clone"] = fmt.Sprintf("%v", s.useClone) 594 params["log-dir"] = s.logDir 595 if s.useAUFS { 596 params["use-aufs"] = "true" 597 } 598 manager, err := lxc.NewContainerManagerForTest( 599 params, &containertesting.MockURLGetter{}, 600 &s.loopDeviceManager, 601 ) 602 c.Assert(err, jc.ErrorIsNil) 603 return manager 604 } 605 606 func (*LxcSuite) TestManagerWarnsAboutUnknownOption(c *gc.C) { 607 _, err := lxc.NewContainerManager(container.ManagerConfig{ 608 container.ConfigName: "BillyBatson", 609 "shazam": "Captain Marvel", 610 }, &containertesting.MockURLGetter{}) 611 c.Assert(err, jc.ErrorIsNil) 612 c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container unused config option: "shazam" -> "Captain Marvel"`) 613 } 614 615 func (s *LxcSuite) TestCreateContainer(c *gc.C) { 616 manager := s.makeManager(c, "test") 617 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 618 619 name := string(instance.Id()) 620 // Check our container config files: initial lxc.conf, the 621 // run-time effective config, and cloud-init userdata. 622 lxcConfContents, err := ioutil.ReadFile(filepath.Join(s.ContainerDir, name, "lxc.conf")) 623 c.Assert(err, jc.ErrorIsNil) 624 c.Assert(string(lxcConfContents), jc.Contains, "lxc.network.link = nic42") 625 lxcConfContents, err = ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 626 c.Assert(err, jc.ErrorIsNil) 627 c.Assert(string(lxcConfContents), jc.Contains, "lxc.network.link = nic42") 628 629 cloudInitFilename := filepath.Join(s.ContainerDir, name, "cloud-init") 630 data := containertesting.AssertCloudInit(c, cloudInitFilename) 631 632 x := make(map[interface{}]interface{}) 633 err = goyaml.Unmarshal(data, &x) 634 c.Assert(err, jc.ErrorIsNil) 635 636 var scripts []string 637 for _, s := range x["runcmd"].([]interface{}) { 638 scripts = append(scripts, s.(string)) 639 } 640 641 c.Assert(scripts[len(scripts)-3:], gc.DeepEquals, []string{ 642 "start jujud-machine-1-lxc-0", 643 "rm $bin/tools.tar.gz && rm $bin/juju2.3.4-quantal-amd64.sha256", 644 "ifconfig", 645 }) 646 647 // Check the mount point has been created inside the container. 648 c.Assert(filepath.Join(s.LxcDir, name, "rootfs", agent.DefaultPaths.LogDir), jc.IsDirectory) 649 // Check that the config file is linked in the restart dir. 650 expectedLinkLocation := filepath.Join(s.RestartDir, name+".conf") 651 expectedTarget := filepath.Join(s.LxcDir, name, "config") 652 linkInfo, err := os.Lstat(expectedLinkLocation) 653 c.Assert(err, jc.ErrorIsNil) 654 c.Assert(linkInfo.Mode()&os.ModeSymlink, gc.Equals, os.ModeSymlink) 655 656 location, err := symlink.Read(expectedLinkLocation) 657 c.Assert(err, jc.ErrorIsNil) 658 c.Assert(location, gc.Equals, expectedTarget) 659 } 660 661 func (s *LxcSuite) TestCreateContainerFailsWithInjectedStartError(c *gc.C) { 662 errorChannel := make(chan error, 1) 663 cleanup := mock.PatchStartTransientErrorInjectionChannel(errorChannel) 664 defer cleanup() 665 666 // One injected error means the container creation will fail 667 // but the destroy function will clean up the remaining container 668 // resulting in a RetryableCreationError 669 errorChannel <- errors.New("start error") 670 671 manager := s.makeManager(c, "test") 672 _, err := containertesting.CreateContainerTest(c, manager, "1/lxc/0") 673 c.Assert(err, gc.NotNil) 674 675 // this should be a retryable error 676 isRetryable := instance.IsRetryableCreationError(errors.Cause(err)) 677 c.Assert(isRetryable, jc.IsTrue) 678 } 679 680 func (s *LxcSuite) TestCreateContainerFailsWithInjectedCreateError(c *gc.C) { 681 errorChannel := make(chan error, 1) 682 cleanup := mock.PatchCreateTransientErrorInjectionChannel(errorChannel) 683 defer cleanup() 684 685 errorChannel <- errors.New("lxc-create error") 686 687 manager := s.makeManager(c, "test") 688 _, err := containertesting.CreateContainerTest(c, manager, "1/lxc/0") 689 c.Assert(err, gc.NotNil) 690 691 // this should be a retryable error 692 isRetryable := instance.IsRetryableCreationError(errors.Cause(err)) 693 c.Assert(isRetryable, jc.IsTrue) 694 c.Assert(err, gc.ErrorMatches, "lxc container creation failed.*") 695 } 696 697 func (s *LxcSuite) TestCreateContainerFailsWithInjectedCloneError(c *gc.C) { 698 errorChannel := make(chan error, 1) 699 cleanup := mock.PatchCloneTransientErrorInjectionChannel(errorChannel) 700 defer cleanup() 701 702 errorChannel <- errors.New("lxc-clone error") 703 704 s.createTemplate(c) 705 s.PatchValue(&s.useClone, true) 706 manager := s.makeManager(c, "test") 707 _, err := containertesting.CreateContainerTest(c, manager, "1") 708 709 // this should be a retryable error 710 isRetryable := instance.IsRetryableCreationError(errors.Cause(err)) 711 c.Assert(isRetryable, jc.IsTrue) 712 c.Assert(err, gc.ErrorMatches, "lxc container cloning failed.*") 713 } 714 715 func (s *LxcSuite) TestCreateContainerWithInjectedErrorDestroyFails(c *gc.C) { 716 errorChannel := make(chan error, 2) 717 cleanup := mock.PatchStartTransientErrorInjectionChannel(errorChannel) 718 defer cleanup() 719 720 // Two injected errors mean that the container creation and subsequent 721 // destroy will fail. This should not result in a RetryableCreationError 722 // as the container was left in an error state 723 errorChannel <- errors.New("create error") 724 errorChannel <- errors.New("destroy error") 725 726 manager := s.makeManager(c, "test") 727 _, err := containertesting.CreateContainerTest(c, manager, "1/lxc/0") 728 c.Assert(err, gc.NotNil) 729 730 // this should not be a retryable error 731 isRetryable := instance.IsRetryableCreationError(errors.Cause(err)) 732 c.Assert(isRetryable, jc.IsFalse) 733 } 734 735 func (s *LxcSuite) ensureTemplateStopped(name string) <-chan struct{} { 736 ch := make(chan struct{}, 1) 737 go func() { 738 for { 739 template := s.ContainerFactory.New(name) 740 if template.IsRunning() { 741 template.Stop() 742 close(ch) 743 return 744 } 745 time.Sleep(50 * time.Millisecond) 746 } 747 }() 748 return ch 749 } 750 751 func (s *LxcSuite) AssertEvent(c *gc.C, event mock.Event, expected mock.Action, id string) { 752 c.Assert(event.Action, gc.Equals, expected) 753 c.Assert(event.InstanceId, gc.Equals, id) 754 if expected == mock.Created { 755 c.Assert(event.EnvArgs, gc.Not(gc.HasLen), 0) 756 } 757 } 758 759 func (s *LxcSuite) TestCreateContainerEvents(c *gc.C) { 760 manager := s.makeManager(c, "test") 761 instance := containertesting.CreateContainer(c, manager, "1") 762 id := string(instance.Id()) 763 s.AssertEvent(c, <-s.events, mock.Created, id) 764 s.AssertEvent(c, <-s.events, mock.Started, id) 765 } 766 767 func (s *LxcSuite) TestCreateContainerEventsWithClone(c *gc.C) { 768 s.PatchValue(&s.useClone, true) 769 // The template containers are created with an upstart job that 770 // stops them once cloud init has finished. We emulate that here. 771 template := "juju-quantal-lxc-template" 772 ch := s.ensureTemplateStopped(template) 773 defer func() { <-ch }() 774 manager := s.makeManager(c, "test") 775 instance := containertesting.CreateContainer(c, manager, "1") 776 id := string(instance.Id()) 777 s.AssertEvent(c, <-s.events, mock.Created, template) 778 s.AssertEvent(c, <-s.events, mock.Started, template) 779 s.AssertEvent(c, <-s.events, mock.Stopped, template) 780 s.AssertEvent(c, <-s.events, mock.Cloned, template) 781 s.AssertEvent(c, <-s.events, mock.Started, id) 782 } 783 784 func (s *LxcSuite) createTemplate(c *gc.C) golxc.Container { 785 name := "juju-quantal-lxc-template" 786 ch := s.ensureTemplateStopped(name) 787 defer func() { <-ch }() 788 network := container.BridgeNetworkConfig("nic42", 4321, nil) 789 authorizedKeys := "authorized keys list" 790 aptProxy := proxy.Settings{} 791 aptMirror := "http://my.archive.ubuntu.com/ubuntu" 792 callback := func(containerStatus status.Status, info string, data map[string]interface{}) error { return nil } 793 template, err := lxc.EnsureCloneTemplate( 794 "ext4", 795 "quantal", 796 network, 797 authorizedKeys, 798 aptProxy, 799 aptMirror, 800 true, 801 true, 802 &containertesting.MockURLGetter{}, 803 false, 804 callback, 805 ) 806 c.Assert(err, jc.ErrorIsNil) 807 c.Assert(template.Name(), gc.Equals, name) 808 809 createEvent := <-s.events 810 c.Assert(createEvent.Action, gc.Equals, mock.Created) 811 c.Assert(createEvent.InstanceId, gc.Equals, name) 812 argsSet := set.NewStrings(createEvent.TemplateArgs...) 813 c.Assert(argsSet.Contains("imageURL"), jc.IsTrue) 814 s.AssertEvent(c, <-s.events, mock.Started, name) 815 s.AssertEvent(c, <-s.events, mock.Stopped, name) 816 817 autostartLink := lxc.RestartSymlink(name) 818 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 819 c.Assert(err, jc.ErrorIsNil) 820 expected := ` 821 # network config 822 # interface "eth0" 823 lxc.network.type = veth 824 lxc.network.link = nic42 825 lxc.network.flags = up 826 lxc.network.mtu = 4321 827 828 ` 829 // NOTE: no autostart, no mounting the log dir 830 c.Assert(string(config), gc.Equals, expected) 831 c.Assert(autostartLink, jc.DoesNotExist) 832 833 return template 834 } 835 836 func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplate(c *gc.C) { 837 s.HookCommandOutput(&lxc.FsCommandOutput, []byte("Type\next4\n"), nil) 838 s.createTemplate(c) 839 s.PatchValue(&s.useClone, true) 840 manager := s.makeManager(c, "test") 841 instance := containertesting.CreateContainer(c, manager, "1") 842 name := string(instance.Id()) 843 cloned := <-s.events 844 s.AssertEvent(c, cloned, mock.Cloned, "juju-quantal-lxc-template") 845 c.Assert(cloned.Args, gc.IsNil) 846 s.AssertEvent(c, <-s.events, mock.Started, name) 847 } 848 849 func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplateAUFS(c *gc.C) { 850 s.HookCommandOutput(&lxc.FsCommandOutput, []byte("Type\next4\n"), nil) 851 s.createTemplate(c) 852 s.PatchValue(&s.useClone, true) 853 s.PatchValue(&s.useAUFS, true) 854 manager := s.makeManager(c, "test") 855 instance := containertesting.CreateContainer(c, manager, "1") 856 name := string(instance.Id()) 857 cloned := <-s.events 858 s.AssertEvent(c, cloned, mock.Cloned, "juju-quantal-lxc-template") 859 c.Assert(cloned.Args, gc.DeepEquals, []string{"--snapshot", "--backingstore", "aufs"}) 860 s.AssertEvent(c, <-s.events, mock.Started, name) 861 } 862 863 func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplateBtrfs(c *gc.C) { 864 s.HookCommandOutput(&lxc.FsCommandOutput, []byte("Type\nbtrfs\n"), nil) 865 s.createTemplate(c) 866 s.PatchValue(&s.useClone, true) 867 manager := s.makeManager(c, "test") 868 instance := containertesting.CreateContainer(c, manager, "1") 869 name := string(instance.Id()) 870 cloned := <-s.events 871 s.AssertEvent(c, cloned, mock.Cloned, "juju-quantal-lxc-template") 872 c.Assert(cloned.Args, gc.DeepEquals, []string{"--snapshot"}) 873 s.AssertEvent(c, <-s.events, mock.Started, name) 874 } 875 876 func (s *LxcSuite) TestCreateContainerWithCloneMountsAndAutostarts(c *gc.C) { 877 s.createTemplate(c) 878 s.PatchValue(&s.useClone, true) 879 manager := s.makeManager(c, "test") 880 instance := containertesting.CreateContainer(c, manager, "1") 881 name := string(instance.Id()) 882 883 autostartLink := lxc.RestartSymlink(name) 884 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 885 c.Assert(err, jc.ErrorIsNil) 886 mountLine := fmt.Sprintf("lxc.mount.entry = %s var/log/juju none defaults,bind 0 0", s.logDir) 887 c.Assert(string(config), jc.Contains, mountLine) 888 c.Assert(autostartLink, jc.IsSymlink) 889 } 890 891 func (s *LxcSuite) TestContainerState(c *gc.C) { 892 // TODO(perrito666) refactor state reporting to return a proper state. 893 manager := s.makeManager(c, "test") 894 c.Logf("%#v", manager) 895 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 896 897 // The mock container will be immediately "running". 898 c.Assert(instance.Status().Message, gc.Equals, string(golxc.StateRunning)) 899 900 // DestroyContainer stops and then destroys the container, putting it 901 // into "unknown" state. 902 err := manager.DestroyContainer(instance.Id()) 903 c.Assert(err, jc.ErrorIsNil) 904 c.Assert(instance.Status().Message, gc.Equals, string(golxc.StateUnknown)) 905 } 906 907 func (s *LxcSuite) TestDestroyContainer(c *gc.C) { 908 manager := s.makeManager(c, "test") 909 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 910 911 err := manager.DestroyContainer(instance.Id()) 912 c.Assert(err, jc.ErrorIsNil) 913 914 name := string(instance.Id()) 915 // Check that the container dir is no longer in the container dir 916 c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist) 917 // but instead, in the removed container dir 918 c.Assert(filepath.Join(s.RemovedDir, name), jc.IsDirectory) 919 920 c.Assert(s.loopDeviceManager.detachLoopDevicesArgs, jc.DeepEquals, [][]string{ 921 {filepath.Join(s.LxcDir, name, "rootfs"), "/"}, 922 }) 923 } 924 925 func (s *LxcSuite) TestDestroyContainerNameClash(c *gc.C) { 926 manager := s.makeManager(c, "test") 927 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 928 929 name := string(instance.Id()) 930 targetDir := filepath.Join(s.RemovedDir, name) 931 err := os.MkdirAll(targetDir, 0755) 932 c.Assert(err, jc.ErrorIsNil) 933 934 err = manager.DestroyContainer(instance.Id()) 935 c.Assert(err, jc.ErrorIsNil) 936 937 // Check that the container dir is no longer in the container dir 938 c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist) 939 // but instead, in the removed container dir with a ".1" suffix as there was already a directory there. 940 c.Assert(filepath.Join(s.RemovedDir, fmt.Sprintf("%s.1", name)), jc.IsDirectory) 941 } 942 943 func (s *LxcSuite) TestNamedManagerPrefix(c *gc.C) { 944 manager := s.makeManager(c, "eric") 945 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 946 c.Assert(string(instance.Id()), gc.Equals, "eric-machine-1-lxc-0") 947 } 948 949 func (s *LxcSuite) TestListContainers(c *gc.C) { 950 foo := s.makeManager(c, "foo") 951 bar := s.makeManager(c, "bar") 952 953 foo1 := containertesting.CreateContainer(c, foo, "1/lxc/0") 954 foo2 := containertesting.CreateContainer(c, foo, "1/lxc/1") 955 foo3 := containertesting.CreateContainer(c, foo, "1/lxc/2") 956 957 bar1 := containertesting.CreateContainer(c, bar, "1/lxc/0") 958 bar2 := containertesting.CreateContainer(c, bar, "1/lxc/1") 959 960 result, err := foo.ListContainers() 961 c.Assert(err, jc.ErrorIsNil) 962 instancetest.MatchInstances(c, result, foo1, foo2, foo3) 963 964 result, err = bar.ListContainers() 965 c.Assert(err, jc.ErrorIsNil) 966 instancetest.MatchInstances(c, result, bar1, bar2) 967 } 968 969 func (s *LxcSuite) TestCreateContainerAutostarts(c *gc.C) { 970 manager := s.makeManager(c, "test") 971 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 972 autostartLink := lxc.RestartSymlink(string(instance.Id())) 973 c.Assert(autostartLink, jc.IsSymlink) 974 } 975 976 func (s *LxcSuite) TestCreateContainerNoRestartDir(c *gc.C) { 977 err := os.Remove(s.RestartDir) 978 c.Assert(err, jc.ErrorIsNil) 979 980 manager := s.makeManager(c, "test") 981 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 982 name := string(instance.Id()) 983 autostartLink := lxc.RestartSymlink(name) 984 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 985 c.Assert(err, jc.ErrorIsNil) 986 expected := fmt.Sprintf(` 987 # network config 988 # interface "eth0" 989 lxc.network.type = veth 990 lxc.network.link = nic42 991 lxc.network.flags = up 992 993 lxc.start.auto = 1 994 lxc.mount.entry = %s var/log/juju none defaults,bind 0 0 995 `, s.logDir) 996 c.Assert(string(config), gc.Equals, expected) 997 c.Assert(autostartLink, jc.DoesNotExist) 998 } 999 1000 func (s *LxcSuite) TestCreateContainerWithBlockStorage(c *gc.C) { 1001 err := os.Remove(s.RestartDir) 1002 c.Assert(err, jc.ErrorIsNil) 1003 1004 manager := s.makeManager(c, "test") 1005 machineConfig, err := containertesting.MockMachineConfig("1/lxc/0") 1006 c.Assert(err, jc.ErrorIsNil) 1007 storageConfig := &container.StorageConfig{AllowMount: true} 1008 networkConfig := container.BridgeNetworkConfig("nic42", 4321, nil) 1009 instance := containertesting.CreateContainerWithMachineAndNetworkAndStorageConfig(c, manager, machineConfig, networkConfig, storageConfig) 1010 name := string(instance.Id()) 1011 autostartLink := lxc.RestartSymlink(name) 1012 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 1013 c.Assert(err, jc.ErrorIsNil) 1014 expected := fmt.Sprintf(` 1015 # network config 1016 # interface "eth0" 1017 lxc.network.type = veth 1018 lxc.network.link = nic42 1019 lxc.network.flags = up 1020 lxc.network.mtu = 4321 1021 1022 lxc.start.auto = 1 1023 lxc.mount.entry = %s var/log/juju none defaults,bind 0 0 1024 1025 lxc.aa_profile = lxc-container-default-with-mounting 1026 lxc.cgroup.devices.allow = b 7:* rwm 1027 lxc.cgroup.devices.allow = c 10:237 rwm 1028 `, s.logDir) 1029 c.Assert(string(config), gc.Equals, expected) 1030 c.Assert(autostartLink, jc.DoesNotExist) 1031 } 1032 1033 func (s *LxcSuite) TestDestroyContainerRemovesAutostartLink(c *gc.C) { 1034 manager := s.makeManager(c, "test") 1035 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 1036 err := manager.DestroyContainer(instance.Id()) 1037 c.Assert(err, jc.ErrorIsNil) 1038 autostartLink := lxc.RestartSymlink(string(instance.Id())) 1039 c.Assert(autostartLink, jc.SymlinkDoesNotExist) 1040 } 1041 1042 func (s *LxcSuite) TestDestroyContainerNoRestartDir(c *gc.C) { 1043 err := os.Remove(s.RestartDir) 1044 c.Assert(err, jc.ErrorIsNil) 1045 1046 manager := s.makeManager(c, "test") 1047 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 1048 err = manager.DestroyContainer(instance.Id()) 1049 c.Assert(err, jc.ErrorIsNil) 1050 } 1051 1052 type NetworkSuite struct { 1053 coretesting.BaseSuite 1054 } 1055 1056 var _ = gc.Suite(&NetworkSuite{}) 1057 1058 func (*NetworkSuite) TestGenerateNetworkConfig(c *gc.C) { 1059 dhcpNIC := network.InterfaceInfo{ 1060 DeviceIndex: 0, 1061 MACAddress: "aa:bb:cc:dd:ee:f0", 1062 InterfaceName: "eth0", 1063 // The following is not part of the LXC config, but cause the 1064 // generated cloud-init user-data to change accordingly. 1065 ConfigType: network.ConfigDHCP, 1066 } 1067 staticNIC := network.InterfaceInfo{ 1068 DeviceIndex: 1, 1069 CIDR: "0.1.2.0/20", // used to infer the subnet mask. 1070 MACAddress: "aa:bb:cc:dd:ee:f1", 1071 InterfaceName: "eth1", 1072 Address: network.NewAddress("0.1.2.3"), 1073 GatewayAddress: network.NewAddress("0.1.2.1"), 1074 // The rest is passed to cloud-init. 1075 ConfigType: network.ConfigStatic, 1076 DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), 1077 } 1078 extraConfigNIC := network.InterfaceInfo{ 1079 DeviceIndex: 2, 1080 MACAddress: "aa:bb:cc:dd:ee:f2", 1081 InterfaceName: "eth2", 1082 VLANTag: 42, 1083 NoAutoStart: true, 1084 // The rest is passed to cloud-init. 1085 ConfigType: network.ConfigManual, 1086 DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), 1087 } 1088 // Test /24 is used by default when the CIDR is invalid or empty. 1089 staticNICNoCIDR, staticNICBadCIDR := staticNIC, staticNIC 1090 staticNICNoCIDR.CIDR = "" 1091 staticNICBadCIDR.CIDR = "bad" 1092 // Test when NoAutoStart is true gateway is not added, even if there. 1093 staticNICNoAutoWithGW := staticNIC 1094 staticNICNoAutoWithGW.NoAutoStart = true 1095 1096 var lastTestLog string 1097 allNICs := []network.InterfaceInfo{dhcpNIC, staticNIC, extraConfigNIC} 1098 for i, test := range []struct { 1099 about string 1100 config *container.NetworkConfig 1101 nics []network.InterfaceInfo 1102 rendered []string 1103 logContains string 1104 logDoesNotContain string 1105 }{{ 1106 about: "empty config", 1107 config: nil, 1108 rendered: []string{ 1109 "lxc.network.type = veth", 1110 "lxc.network.link = lxcbr0", 1111 "lxc.network.flags = up", 1112 }, 1113 logContains: `WARNING juju.container.lxc network type missing, using the default "bridge" config`, 1114 logDoesNotContain: `INFO juju.container.lxc setting MTU to 0 for LXC network interfaces`, 1115 }, { 1116 about: "default config", 1117 config: lxc.DefaultNetworkConfig(), 1118 rendered: []string{ 1119 "lxc.network.type = veth", 1120 "lxc.network.link = lxcbr0", 1121 "lxc.network.flags = up", 1122 }, 1123 logDoesNotContain: `INFO juju.container.lxc setting MTU to 0 for LXC network interfaces`, 1124 }, { 1125 about: "bridge config with MTU 1500, device foo, no NICs", 1126 config: container.BridgeNetworkConfig("foo", 1500, nil), 1127 rendered: []string{ 1128 "lxc.network.type = veth", 1129 "lxc.network.link = foo", 1130 "lxc.network.flags = up", 1131 "lxc.network.mtu = 1500", 1132 }, 1133 }, { 1134 about: "phys config with MTU 9000, device foo, no NICs", 1135 config: container.PhysicalNetworkConfig("foo", 9000, nil), 1136 rendered: []string{ 1137 "lxc.network.type = phys", 1138 "lxc.network.link = foo", 1139 "lxc.network.flags = up", 1140 "lxc.network.mtu = 9000", 1141 }, 1142 }, { 1143 about: "bridge config with MTU 8000, device foo, all NICs", 1144 config: container.BridgeNetworkConfig("foo", 8000, allNICs), 1145 nics: allNICs, 1146 rendered: []string{ 1147 "lxc.network.type = veth", 1148 "lxc.network.link = foo", 1149 "lxc.network.flags = up", 1150 "lxc.network.name = eth0", 1151 "lxc.network.hwaddr = aa:bb:cc:dd:ee:f0", 1152 1153 "lxc.network.type = veth", 1154 "lxc.network.link = foo", 1155 "lxc.network.flags = up", 1156 "lxc.network.name = eth1", 1157 "lxc.network.hwaddr = aa:bb:cc:dd:ee:f1", 1158 "lxc.network.ipv4 = 0.1.2.3/20", 1159 1160 "lxc.network.type = veth", 1161 "lxc.network.link = foo", 1162 "lxc.network.name = eth2", 1163 "lxc.network.hwaddr = aa:bb:cc:dd:ee:f2", 1164 }, 1165 }, { 1166 about: "bridge config with MTU 0, device foo, staticNICNoCIDR", 1167 config: container.BridgeNetworkConfig("foo", 0, []network.InterfaceInfo{staticNICNoCIDR}), 1168 nics: []network.InterfaceInfo{staticNICNoCIDR}, 1169 rendered: []string{ 1170 "lxc.network.type = veth", 1171 "lxc.network.link = foo", 1172 "lxc.network.flags = up", 1173 "lxc.network.name = eth1", 1174 "lxc.network.hwaddr = aa:bb:cc:dd:ee:f1", 1175 }, 1176 logDoesNotContain: `INFO juju.container.lxc setting MTU to 0 for all LXC network interfaces`, 1177 }, { 1178 about: "bridge config with MTU 0, device foo, staticNICBadCIDR", 1179 config: container.BridgeNetworkConfig("foo", 0, []network.InterfaceInfo{staticNICBadCIDR}), 1180 nics: []network.InterfaceInfo{staticNICBadCIDR}, 1181 rendered: []string{ 1182 "lxc.network.type = veth", 1183 "lxc.network.link = foo", 1184 "lxc.network.flags = up", 1185 "lxc.network.name = eth1", 1186 "lxc.network.hwaddr = aa:bb:cc:dd:ee:f1", 1187 "lxc.network.ipv4 = invalid CIDR address: bad", 1188 }, 1189 }, { 1190 about: "bridge config with MTU 0, device foo, staticNICNoAutoWithGW", 1191 config: container.BridgeNetworkConfig("foo", 0, []network.InterfaceInfo{staticNICNoAutoWithGW}), 1192 nics: []network.InterfaceInfo{staticNICNoAutoWithGW}, 1193 rendered: []string{ 1194 "lxc.network.type = veth", 1195 "lxc.network.link = foo", 1196 "lxc.network.name = eth1", 1197 "lxc.network.hwaddr = aa:bb:cc:dd:ee:f1", 1198 "lxc.network.ipv4 = 0.1.2.3/20", 1199 }, 1200 }} { 1201 c.Logf("test #%d: %s", i, test.about) 1202 config := lxc.GenerateNetworkConfig(test.config) 1203 // Parse the config to drop comments and empty lines. This is 1204 // needed to ensure the order of all settings match what we 1205 // expect to get rendered, as the order matters. 1206 var configLines []string 1207 for _, line := range strings.Split(config, "\n") { 1208 line = strings.TrimSpace(line) 1209 if line == "" || strings.HasPrefix(line, "#") { 1210 continue 1211 } 1212 configLines = append(configLines, line) 1213 } 1214 currentLog := strings.TrimPrefix(c.GetTestLog(), lastTestLog) 1215 c.Check(configLines, jc.DeepEquals, test.rendered) 1216 if test.logContains != "" { 1217 c.Check(currentLog, jc.Contains, test.logContains) 1218 } 1219 if test.logDoesNotContain != "" { 1220 c.Check(currentLog, gc.Not(jc.Contains), test.logDoesNotContain) 1221 } 1222 // TODO(dimitern) In a follow-up, test the generated user-data 1223 // honors the other settings. 1224 lastTestLog = c.GetTestLog() 1225 } 1226 } 1227 1228 func (*NetworkSuite) TestNetworkConfigTemplate(c *gc.C) { 1229 // Intentionally using an invalid type "foo" here to test it gets 1230 // changed to the default "veth" and a warning is logged. 1231 config := lxc.NetworkConfigTemplate(container.NetworkConfig{"foo", "bar", 4321, nil}) 1232 // In the past, the entire lxc.conf file was just networking. With 1233 // the addition of the auto start, we now have to have better 1234 // isolate this test. As such, we parse the conf template results 1235 // and just get the results that start with 'lxc.network' as that 1236 // is what the test cares about. 1237 obtained := []string{} 1238 for _, value := range strings.Split(config, "\n") { 1239 if strings.HasPrefix(value, "lxc.network") { 1240 obtained = append(obtained, value) 1241 } 1242 } 1243 expected := []string{ 1244 "lxc.network.type = veth", 1245 "lxc.network.link = bar", 1246 "lxc.network.flags = up", 1247 "lxc.network.mtu = 4321", 1248 } 1249 c.Assert(obtained, jc.DeepEquals, expected) 1250 log := c.GetTestLog() 1251 c.Assert(log, jc.Contains, 1252 `WARNING juju.container.lxc unknown network type "foo", using the default "bridge" config`, 1253 ) 1254 } 1255 1256 func (s *LxcSuite) TestWgetEnvironmentUsesNoProxy(c *gc.C) { 1257 var wgetScript []byte 1258 fakeCert := []byte("fakeCert") 1259 s.PatchValue(lxc.WriteWgetTmpFile, func(filename string, data []byte, perm os.FileMode) error { 1260 wgetScript = data 1261 return nil 1262 }) 1263 _, closer, err := lxc.WgetEnvironment(fakeCert) 1264 c.Assert(err, jc.ErrorIsNil) 1265 defer closer() 1266 c.Assert(string(wgetScript), jc.Contains, "/usr/bin/wget --no-proxy --ca-certificate") 1267 } 1268 1269 type mockLoopDeviceManager struct { 1270 detachLoopDevicesArgs [][]string 1271 } 1272 1273 func (m *mockLoopDeviceManager) DetachLoopDevices(rootfs, prefix string) error { 1274 m.detachLoopDevicesArgs = append(m.detachLoopDevicesArgs, []string{rootfs, prefix}) 1275 return nil 1276 }