github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 "strings" 12 stdtesting "testing" 13 "time" 14 15 "github.com/juju/loggo" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils/proxy" 18 gc "launchpad.net/gocheck" 19 "launchpad.net/golxc" 20 "launchpad.net/goyaml" 21 22 "github.com/juju/juju/agent" 23 "github.com/juju/juju/container" 24 "github.com/juju/juju/container/lxc" 25 "github.com/juju/juju/container/lxc/mock" 26 lxctesting "github.com/juju/juju/container/lxc/testing" 27 containertesting "github.com/juju/juju/container/testing" 28 instancetest "github.com/juju/juju/instance/testing" 29 coretesting "github.com/juju/juju/testing" 30 ) 31 32 func Test(t *stdtesting.T) { 33 gc.TestingT(t) 34 } 35 36 type LxcSuite struct { 37 lxctesting.TestSuite 38 39 events chan mock.Event 40 useClone bool 41 useAUFS bool 42 } 43 44 var _ = gc.Suite(&LxcSuite{}) 45 46 func (s *LxcSuite) SetUpTest(c *gc.C) { 47 s.TestSuite.SetUpTest(c) 48 loggo.GetLogger("juju.container.lxc").SetLogLevel(loggo.TRACE) 49 s.events = make(chan mock.Event, 25) 50 s.TestSuite.Factory.AddListener(s.events) 51 s.PatchValue(&lxc.TemplateLockDir, c.MkDir()) 52 s.PatchValue(&lxc.TemplateStopTimeout, 500*time.Millisecond) 53 } 54 55 func (s *LxcSuite) TearDownTest(c *gc.C) { 56 s.TestSuite.Factory.RemoveListener(s.events) 57 close(s.events) 58 s.TestSuite.TearDownTest(c) 59 } 60 61 func (t *LxcSuite) TestPreferFastLXC(c *gc.C) { 62 for i, test := range []struct { 63 message string 64 releaseVersion string 65 expected bool 66 }{{ 67 message: "missing release file", 68 }, { 69 message: "precise release", 70 releaseVersion: "12.04", 71 }, { 72 message: "trusty release", 73 releaseVersion: "14.04", 74 expected: true, 75 }, { 76 message: "unstable unicorn", 77 releaseVersion: "14.10", 78 expected: true, 79 }, { 80 message: "lucid", 81 releaseVersion: "10.04", 82 }} { 83 c.Logf("%v: %v", i, test.message) 84 value := lxc.PreferFastLXC(test.releaseVersion) 85 c.Assert(value, gc.Equals, test.expected) 86 } 87 } 88 89 func (s *LxcSuite) TestContainerManagerLXCClone(c *gc.C) { 90 type test struct { 91 releaseVersion string 92 useClone string 93 expectClone bool 94 } 95 tests := []test{{ 96 releaseVersion: "12.04", 97 useClone: "true", 98 expectClone: true, 99 }, { 100 releaseVersion: "14.04", 101 expectClone: true, 102 }, { 103 releaseVersion: "12.04", 104 useClone: "false", 105 }, { 106 releaseVersion: "14.04", 107 useClone: "false", 108 }} 109 110 for i, test := range tests { 111 c.Logf("test %d: %v", i, test) 112 s.PatchValue(lxc.ReleaseVersion, func() string { return test.releaseVersion }) 113 114 mgr, err := lxc.NewContainerManager(container.ManagerConfig{ 115 container.ConfigName: "juju", 116 "use-clone": test.useClone, 117 }) 118 c.Assert(err, gc.IsNil) 119 c.Check(lxc.GetCreateWithCloneValue(mgr), gc.Equals, test.expectClone) 120 } 121 } 122 123 func (s *LxcSuite) TestContainerDirFilesystem(c *gc.C) { 124 for i, test := range []struct { 125 message string 126 output string 127 expected string 128 errorMatch string 129 }{{ 130 message: "btrfs", 131 output: "Type\nbtrfs\n", 132 expected: lxc.Btrfs, 133 }, { 134 message: "ext4", 135 output: "Type\next4\n", 136 expected: "ext4", 137 }, { 138 message: "not enough output", 139 output: "foo", 140 errorMatch: "could not determine filesystem type", 141 }} { 142 c.Logf("%v: %s", i, test.message) 143 s.HookCommandOutput(&lxc.FsCommandOutput, []byte(test.output), nil) 144 value, err := lxc.ContainerDirFilesystem() 145 if test.errorMatch == "" { 146 c.Check(err, gc.IsNil) 147 c.Check(value, gc.Equals, test.expected) 148 } else { 149 c.Check(err, gc.ErrorMatches, test.errorMatch) 150 } 151 } 152 } 153 154 func (s *LxcSuite) makeManager(c *gc.C, name string) container.Manager { 155 params := container.ManagerConfig{ 156 container.ConfigName: name, 157 } 158 // Need to ensure use-clone is explicitly set to avoid it 159 // being set based on the OS version. 160 params["use-clone"] = fmt.Sprintf("%v", s.useClone) 161 if s.useAUFS { 162 params["use-aufs"] = "true" 163 } 164 manager, err := lxc.NewContainerManager(params) 165 c.Assert(err, gc.IsNil) 166 return manager 167 } 168 169 func (*LxcSuite) TestManagerWarnsAboutUnknownOption(c *gc.C) { 170 _, err := lxc.NewContainerManager(container.ManagerConfig{ 171 container.ConfigName: "BillyBatson", 172 "shazam": "Captain Marvel", 173 }) 174 c.Assert(err, gc.IsNil) 175 c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container unused config option: "shazam" -> "Captain Marvel"`) 176 } 177 178 func (s *LxcSuite) TestCreateContainer(c *gc.C) { 179 manager := s.makeManager(c, "test") 180 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 181 182 name := string(instance.Id()) 183 // Check our container config files. 184 lxcConfContents, err := ioutil.ReadFile(filepath.Join(s.ContainerDir, name, "lxc.conf")) 185 c.Assert(err, gc.IsNil) 186 c.Assert(string(lxcConfContents), jc.Contains, "lxc.network.link = nic42") 187 188 cloudInitFilename := filepath.Join(s.ContainerDir, name, "cloud-init") 189 data := containertesting.AssertCloudInit(c, cloudInitFilename) 190 191 x := make(map[interface{}]interface{}) 192 err = goyaml.Unmarshal(data, &x) 193 c.Assert(err, gc.IsNil) 194 195 var scripts []string 196 for _, s := range x["runcmd"].([]interface{}) { 197 scripts = append(scripts, s.(string)) 198 } 199 200 c.Assert(scripts[len(scripts)-2:], gc.DeepEquals, []string{ 201 "start jujud-machine-1-lxc-0", 202 "ifconfig", 203 }) 204 205 // Check the mount point has been created inside the container. 206 c.Assert(filepath.Join(s.LxcDir, name, "rootfs", agent.DefaultLogDir), jc.IsDirectory) 207 // Check that the config file is linked in the restart dir. 208 expectedLinkLocation := filepath.Join(s.RestartDir, name+".conf") 209 expectedTarget := filepath.Join(s.LxcDir, name, "config") 210 linkInfo, err := os.Lstat(expectedLinkLocation) 211 c.Assert(err, gc.IsNil) 212 c.Assert(linkInfo.Mode()&os.ModeSymlink, gc.Equals, os.ModeSymlink) 213 214 location, err := os.Readlink(expectedLinkLocation) 215 c.Assert(err, gc.IsNil) 216 c.Assert(location, gc.Equals, expectedTarget) 217 } 218 219 func (s *LxcSuite) ensureTemplateStopped(name string) { 220 go func() { 221 for { 222 template := s.Factory.New(name) 223 if template.IsRunning() { 224 template.Stop() 225 } 226 time.Sleep(50 * time.Millisecond) 227 } 228 }() 229 } 230 231 func (s *LxcSuite) AssertEvent(c *gc.C, event mock.Event, expected mock.Action, id string) { 232 c.Assert(event.Action, gc.Equals, expected) 233 c.Assert(event.InstanceId, gc.Equals, id) 234 } 235 236 func (s *LxcSuite) TestCreateContainerEvents(c *gc.C) { 237 manager := s.makeManager(c, "test") 238 instance := containertesting.CreateContainer(c, manager, "1") 239 id := string(instance.Id()) 240 s.AssertEvent(c, <-s.events, mock.Created, id) 241 s.AssertEvent(c, <-s.events, mock.Started, id) 242 } 243 244 func (s *LxcSuite) TestCreateContainerEventsWithClone(c *gc.C) { 245 s.PatchValue(&s.useClone, true) 246 // The template containers are created with an upstart job that 247 // stops them once cloud init has finished. We emulate that here. 248 template := "juju-series-template" 249 s.ensureTemplateStopped(template) 250 manager := s.makeManager(c, "test") 251 instance := containertesting.CreateContainer(c, manager, "1") 252 id := string(instance.Id()) 253 s.AssertEvent(c, <-s.events, mock.Created, template) 254 s.AssertEvent(c, <-s.events, mock.Started, template) 255 s.AssertEvent(c, <-s.events, mock.Stopped, template) 256 s.AssertEvent(c, <-s.events, mock.Cloned, template) 257 s.AssertEvent(c, <-s.events, mock.Started, id) 258 } 259 260 func (s *LxcSuite) createTemplate(c *gc.C) golxc.Container { 261 name := "juju-series-template" 262 s.ensureTemplateStopped(name) 263 network := container.BridgeNetworkConfig("nic42") 264 authorizedKeys := "authorized keys list" 265 aptProxy := proxy.Settings{} 266 template, err := lxc.EnsureCloneTemplate( 267 "ext4", "series", network, authorizedKeys, aptProxy) 268 c.Assert(err, gc.IsNil) 269 c.Assert(template.Name(), gc.Equals, name) 270 s.AssertEvent(c, <-s.events, mock.Created, name) 271 s.AssertEvent(c, <-s.events, mock.Started, name) 272 s.AssertEvent(c, <-s.events, mock.Stopped, name) 273 274 autostartLink := lxc.RestartSymlink(name) 275 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 276 c.Assert(err, gc.IsNil) 277 expected := ` 278 lxc.network.type = veth 279 lxc.network.link = nic42 280 lxc.network.flags = up 281 ` 282 // NOTE: no autostart, no mounting the log dir 283 c.Assert(string(config), gc.Equals, expected) 284 c.Assert(autostartLink, jc.DoesNotExist) 285 286 return template 287 } 288 289 func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplate(c *gc.C) { 290 s.createTemplate(c) 291 s.PatchValue(&s.useClone, true) 292 manager := s.makeManager(c, "test") 293 instance := containertesting.CreateContainer(c, manager, "1") 294 name := string(instance.Id()) 295 cloned := <-s.events 296 s.AssertEvent(c, cloned, mock.Cloned, "juju-series-template") 297 c.Assert(cloned.Args, gc.IsNil) 298 s.AssertEvent(c, <-s.events, mock.Started, name) 299 } 300 301 func (s *LxcSuite) TestCreateContainerEventsWithCloneExistingTemplateAUFS(c *gc.C) { 302 s.createTemplate(c) 303 s.PatchValue(&s.useClone, true) 304 s.PatchValue(&s.useAUFS, true) 305 manager := s.makeManager(c, "test") 306 instance := containertesting.CreateContainer(c, manager, "1") 307 name := string(instance.Id()) 308 cloned := <-s.events 309 s.AssertEvent(c, cloned, mock.Cloned, "juju-series-template") 310 c.Assert(cloned.Args, gc.DeepEquals, []string{"--snapshot", "--backingstore", "aufs"}) 311 s.AssertEvent(c, <-s.events, mock.Started, name) 312 } 313 314 func (s *LxcSuite) TestCreateContainerWithCloneMountsAndAutostarts(c *gc.C) { 315 s.createTemplate(c) 316 s.PatchValue(&s.useClone, true) 317 manager := s.makeManager(c, "test") 318 instance := containertesting.CreateContainer(c, manager, "1") 319 name := string(instance.Id()) 320 321 autostartLink := lxc.RestartSymlink(name) 322 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 323 c.Assert(err, gc.IsNil) 324 mountLine := "lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0" 325 c.Assert(string(config), jc.Contains, mountLine) 326 c.Assert(autostartLink, jc.IsSymlink) 327 } 328 329 func (s *LxcSuite) TestContainerState(c *gc.C) { 330 manager := s.makeManager(c, "test") 331 c.Logf("%#v", manager) 332 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 333 334 // The mock container will be immediately "running". 335 c.Assert(instance.Status(), gc.Equals, string(golxc.StateRunning)) 336 337 // DestroyContainer stops and then destroys the container, putting it 338 // into "unknown" state. 339 err := manager.DestroyContainer(instance.Id()) 340 c.Assert(err, gc.IsNil) 341 c.Assert(instance.Status(), gc.Equals, string(golxc.StateUnknown)) 342 } 343 344 func (s *LxcSuite) TestDestroyContainer(c *gc.C) { 345 manager := s.makeManager(c, "test") 346 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 347 348 err := manager.DestroyContainer(instance.Id()) 349 c.Assert(err, gc.IsNil) 350 351 name := string(instance.Id()) 352 // Check that the container dir is no longer in the container dir 353 c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist) 354 // but instead, in the removed container dir 355 c.Assert(filepath.Join(s.RemovedDir, name), jc.IsDirectory) 356 } 357 358 func (s *LxcSuite) TestDestroyContainerNameClash(c *gc.C) { 359 manager := s.makeManager(c, "test") 360 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 361 362 name := string(instance.Id()) 363 targetDir := filepath.Join(s.RemovedDir, name) 364 err := os.MkdirAll(targetDir, 0755) 365 c.Assert(err, gc.IsNil) 366 367 err = manager.DestroyContainer(instance.Id()) 368 c.Assert(err, gc.IsNil) 369 370 // Check that the container dir is no longer in the container dir 371 c.Assert(filepath.Join(s.ContainerDir, name), jc.DoesNotExist) 372 // but instead, in the removed container dir with a ".1" suffix as there was already a directory there. 373 c.Assert(filepath.Join(s.RemovedDir, fmt.Sprintf("%s.1", name)), jc.IsDirectory) 374 } 375 376 func (s *LxcSuite) TestNamedManagerPrefix(c *gc.C) { 377 manager := s.makeManager(c, "eric") 378 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 379 c.Assert(string(instance.Id()), gc.Equals, "eric-machine-1-lxc-0") 380 } 381 382 func (s *LxcSuite) TestListContainers(c *gc.C) { 383 foo := s.makeManager(c, "foo") 384 bar := s.makeManager(c, "bar") 385 386 foo1 := containertesting.CreateContainer(c, foo, "1/lxc/0") 387 foo2 := containertesting.CreateContainer(c, foo, "1/lxc/1") 388 foo3 := containertesting.CreateContainer(c, foo, "1/lxc/2") 389 390 bar1 := containertesting.CreateContainer(c, bar, "1/lxc/0") 391 bar2 := containertesting.CreateContainer(c, bar, "1/lxc/1") 392 393 result, err := foo.ListContainers() 394 c.Assert(err, gc.IsNil) 395 instancetest.MatchInstances(c, result, foo1, foo2, foo3) 396 397 result, err = bar.ListContainers() 398 c.Assert(err, gc.IsNil) 399 instancetest.MatchInstances(c, result, bar1, bar2) 400 } 401 402 func (s *LxcSuite) TestCreateContainerAutostarts(c *gc.C) { 403 manager := s.makeManager(c, "test") 404 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 405 autostartLink := lxc.RestartSymlink(string(instance.Id())) 406 c.Assert(autostartLink, jc.IsSymlink) 407 } 408 409 func (s *LxcSuite) TestCreateContainerNoRestartDir(c *gc.C) { 410 err := os.Remove(s.RestartDir) 411 c.Assert(err, gc.IsNil) 412 413 manager := s.makeManager(c, "test") 414 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 415 name := string(instance.Id()) 416 autostartLink := lxc.RestartSymlink(name) 417 config, err := ioutil.ReadFile(lxc.ContainerConfigFilename(name)) 418 c.Assert(err, gc.IsNil) 419 expected := ` 420 lxc.network.type = veth 421 lxc.network.link = nic42 422 lxc.network.flags = up 423 lxc.start.auto = 1 424 lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0 425 ` 426 c.Assert(string(config), gc.Equals, expected) 427 c.Assert(autostartLink, jc.DoesNotExist) 428 } 429 430 func (s *LxcSuite) TestDestroyContainerRemovesAutostartLink(c *gc.C) { 431 manager := s.makeManager(c, "test") 432 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 433 err := manager.DestroyContainer(instance.Id()) 434 c.Assert(err, gc.IsNil) 435 autostartLink := lxc.RestartSymlink(string(instance.Id())) 436 c.Assert(autostartLink, jc.SymlinkDoesNotExist) 437 } 438 439 func (s *LxcSuite) TestDestroyContainerNoRestartDir(c *gc.C) { 440 err := os.Remove(s.RestartDir) 441 c.Assert(err, gc.IsNil) 442 443 manager := s.makeManager(c, "test") 444 instance := containertesting.CreateContainer(c, manager, "1/lxc/0") 445 err = manager.DestroyContainer(instance.Id()) 446 c.Assert(err, gc.IsNil) 447 } 448 449 type NetworkSuite struct { 450 coretesting.BaseSuite 451 } 452 453 var _ = gc.Suite(&NetworkSuite{}) 454 455 func (*NetworkSuite) TestGenerateNetworkConfig(c *gc.C) { 456 for _, test := range []struct { 457 config *container.NetworkConfig 458 net string 459 link string 460 }{{ 461 config: nil, 462 net: "veth", 463 link: "lxcbr0", 464 }, { 465 config: lxc.DefaultNetworkConfig(), 466 net: "veth", 467 link: "lxcbr0", 468 }, { 469 config: container.BridgeNetworkConfig("foo"), 470 net: "veth", 471 link: "foo", 472 }, { 473 config: container.PhysicalNetworkConfig("foo"), 474 net: "phys", 475 link: "foo", 476 }} { 477 config := lxc.GenerateNetworkConfig(test.config) 478 c.Assert(config, jc.Contains, fmt.Sprintf("lxc.network.type = %s\n", test.net)) 479 c.Assert(config, jc.Contains, fmt.Sprintf("lxc.network.link = %s\n", test.link)) 480 } 481 } 482 483 func (*NetworkSuite) TestNetworkConfigTemplate(c *gc.C) { 484 config := lxc.NetworkConfigTemplate("foo", "bar") 485 //In the past, the entire lxc.conf file was just networking. With the addition 486 //of the auto start, we now have to have better isolate this test. As such, we 487 //parse the conf template results and just get the results that start with 488 //'lxc.network' as that is what the test cares about. 489 obtained := []string{} 490 for _, value := range strings.Split(config, "\n") { 491 if strings.HasPrefix(value, "lxc.network") { 492 obtained = append(obtained, value) 493 } 494 } 495 expected := []string{ 496 "lxc.network.type = foo", 497 "lxc.network.link = bar", 498 "lxc.network.flags = up", 499 } 500 c.Assert(obtained, gc.DeepEquals, expected) 501 }