github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/worker/provisioner/lxc-broker_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "path/filepath" 12 "runtime" 13 "text/template" 14 "time" 15 16 "github.com/juju/errors" 17 "github.com/juju/names" 18 gitjujutesting "github.com/juju/testing" 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/utils/arch" 21 "github.com/juju/utils/series" 22 "github.com/juju/utils/set" 23 "github.com/juju/version" 24 gc "gopkg.in/check.v1" 25 26 "github.com/juju/juju/agent" 27 "github.com/juju/juju/apiserver/params" 28 "github.com/juju/juju/cloudconfig/instancecfg" 29 "github.com/juju/juju/constraints" 30 "github.com/juju/juju/container" 31 "github.com/juju/juju/container/lxc/mock" 32 lxctesting "github.com/juju/juju/container/lxc/testing" 33 containertesting "github.com/juju/juju/container/testing" 34 "github.com/juju/juju/environs" 35 "github.com/juju/juju/environs/filestorage" 36 envtesting "github.com/juju/juju/environs/testing" 37 "github.com/juju/juju/environs/tools" 38 "github.com/juju/juju/feature" 39 "github.com/juju/juju/instance" 40 instancetest "github.com/juju/juju/instance/testing" 41 jujutesting "github.com/juju/juju/juju/testing" 42 "github.com/juju/juju/network" 43 "github.com/juju/juju/state" 44 "github.com/juju/juju/status" 45 "github.com/juju/juju/storage" 46 "github.com/juju/juju/storage/provider" 47 coretesting "github.com/juju/juju/testing" 48 coretools "github.com/juju/juju/tools" 49 jujuversion "github.com/juju/juju/version" 50 "github.com/juju/juju/worker/provisioner" 51 ) 52 53 type lxcSuite struct { 54 lxctesting.TestSuite 55 events chan mock.Event 56 eventsDone chan struct{} 57 } 58 59 type lxcBrokerSuite struct { 60 lxcSuite 61 broker environs.InstanceBroker 62 agentConfig agent.ConfigSetterWriter 63 api *fakeAPI 64 } 65 66 var _ = gc.Suite(&lxcBrokerSuite{}) 67 68 func (s *lxcSuite) SetUpTest(c *gc.C) { 69 s.TestSuite.SetUpTest(c) 70 if runtime.GOOS == "windows" { 71 c.Skip("Skipping lxc tests on windows") 72 } 73 s.events = make(chan mock.Event) 74 s.eventsDone = make(chan struct{}) 75 go func() { 76 defer close(s.eventsDone) 77 for event := range s.events { 78 c.Output(3, fmt.Sprintf("lxc event: <%s, %s>", event.Action, event.InstanceId)) 79 } 80 }() 81 s.TestSuite.ContainerFactory.AddListener(s.events) 82 } 83 84 func (s *lxcSuite) TearDownTest(c *gc.C) { 85 close(s.events) 86 <-s.eventsDone 87 s.TestSuite.TearDownTest(c) 88 } 89 90 func (s *lxcBrokerSuite) SetUpTest(c *gc.C) { 91 if runtime.GOOS == "windows" { 92 c.Skip("Skipping lxc tests on windows") 93 } 94 s.lxcSuite.SetUpTest(c) 95 var err error 96 s.agentConfig, err = agent.NewAgentConfig( 97 agent.AgentConfigParams{ 98 Paths: agent.NewPathsWithDefaults(agent.Paths{DataDir: "/not/used/here"}), 99 Tag: names.NewMachineTag("1"), 100 UpgradedToVersion: jujuversion.Current, 101 Password: "dummy-secret", 102 Nonce: "nonce", 103 APIAddresses: []string{"10.0.0.1:1234"}, 104 CACert: coretesting.CACert, 105 Model: coretesting.ModelTag, 106 }) 107 c.Assert(err, jc.ErrorIsNil) 108 managerConfig := container.ManagerConfig{ 109 container.ConfigName: "juju", 110 "log-dir": c.MkDir(), 111 "use-clone": "false", 112 } 113 s.api = NewFakeAPI() 114 s.broker, err = provisioner.NewLxcBroker(s.api, s.agentConfig, managerConfig, nil, false, 0) 115 c.Assert(err, jc.ErrorIsNil) 116 } 117 118 func (s *lxcBrokerSuite) instanceConfig(c *gc.C, machineId string) *instancecfg.InstanceConfig { 119 machineNonce := "fake-nonce" 120 // To isolate the tests from the host's architecture, we override it here. 121 s.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) 122 stateInfo := jujutesting.FakeStateInfo(machineId) 123 apiInfo := jujutesting.FakeAPIInfo(machineId) 124 instanceConfig, err := instancecfg.NewInstanceConfig(machineId, machineNonce, "released", "quantal", "", true, stateInfo, apiInfo) 125 c.Assert(err, jc.ErrorIsNil) 126 // Ensure the <rootfs>/etc/network path exists. 127 containertesting.EnsureLXCRootFSEtcNetwork(c, "juju-"+names.NewMachineTag(machineId).String()) 128 return instanceConfig 129 } 130 131 func (s *lxcBrokerSuite) startInstance(c *gc.C, machineId string, volumes []storage.VolumeParams) instance.Instance { 132 instanceConfig := s.instanceConfig(c, machineId) 133 cons := constraints.Value{} 134 possibleTools := coretools.List{&coretools.Tools{ 135 Version: version.MustParseBinary("2.3.4-quantal-amd64"), 136 URL: "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz", 137 }, { 138 // non-host-arch tools should be filtered out by StartInstance 139 Version: version.MustParseBinary("2.3.4-quantal-arm64"), 140 URL: "http://tools.testing.invalid/2.3.4-quantal-arm64.tgz", 141 }} 142 callback := func(settableStatus status.Status, info string, data map[string]interface{}) error { 143 return nil 144 } 145 result, err := s.broker.StartInstance(environs.StartInstanceParams{ 146 Constraints: cons, 147 Tools: possibleTools, 148 InstanceConfig: instanceConfig, 149 Volumes: volumes, 150 StatusCallback: callback, 151 }) 152 c.Assert(err, jc.ErrorIsNil) 153 return result.Instance 154 } 155 156 func (s *lxcBrokerSuite) maintainInstance(c *gc.C, machineId string, volumes []storage.VolumeParams) { 157 instanceConfig := s.instanceConfig(c, machineId) 158 cons := constraints.Value{} 159 possibleTools := coretools.List{&coretools.Tools{ 160 Version: version.MustParseBinary("2.3.4-quantal-amd64"), 161 URL: "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz", 162 }} 163 callback := func(settableStatus status.Status, info string, data map[string]interface{}) error { 164 return nil 165 } 166 err := s.broker.MaintainInstance(environs.StartInstanceParams{ 167 Constraints: cons, 168 Tools: possibleTools, 169 InstanceConfig: instanceConfig, 170 Volumes: volumes, 171 StatusCallback: callback, 172 }) 173 c.Assert(err, jc.ErrorIsNil) 174 } 175 176 func (s *lxcBrokerSuite) assertDefaultStorageConfig(c *gc.C, lxc instance.Instance) { 177 config := filepath.Join(s.LxcDir, string(lxc.Id()), "config") 178 AssertFileContents(c, gc.Not(jc.Contains), config, "lxc.aa_profile = lxc-container-default-with-mounting") 179 } 180 181 func (s *lxcBrokerSuite) assertDefaultNetworkConfig(c *gc.C, lxc instance.Instance) { 182 lxc_conf := filepath.Join(s.ContainerDir, string(lxc.Id()), "lxc.conf") 183 expect := []string{ 184 "lxc.network.type = veth", 185 "lxc.network.link = lxcbr0", 186 } 187 AssertFileContains(c, lxc_conf, expect...) 188 } 189 190 func (s *lxcBrokerSuite) TestStartInstance(c *gc.C) { 191 machineId := "1/lxc/0" 192 s.SetFeatureFlags(feature.AddressAllocation) 193 lxc := s.startInstance(c, machineId, nil) 194 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 195 FuncName: "ContainerConfig", 196 }, { 197 FuncName: "PrepareContainerInterfaceInfo", 198 Args: []interface{}{names.NewMachineTag("1-lxc-0")}, 199 }}) 200 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 201 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 202 s.assertInstances(c, lxc) 203 s.assertDefaultNetworkConfig(c, lxc) 204 s.assertDefaultStorageConfig(c, lxc) 205 } 206 207 func (s *lxcBrokerSuite) TestStartInstanceAddressAllocationDisabled(c *gc.C) { 208 machineId := "1/lxc/0" 209 lxc := s.startInstance(c, machineId, nil) 210 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 211 FuncName: "ContainerConfig", 212 }, { 213 FuncName: "PrepareContainerInterfaceInfo", 214 Args: []interface{}{names.NewMachineTag("1-lxc-0")}, 215 }}) 216 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 217 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 218 s.assertInstances(c, lxc) 219 s.assertDefaultNetworkConfig(c, lxc) 220 s.assertDefaultStorageConfig(c, lxc) 221 } 222 223 func (s *lxcBrokerSuite) TestMaintainInstance(c *gc.C) { 224 machineId := "1/lxc/0" 225 s.SetFeatureFlags(feature.AddressAllocation) 226 lxc := s.startInstance(c, machineId, nil) 227 s.api.ResetCalls() 228 229 s.maintainInstance(c, machineId, nil) 230 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 231 FuncName: "GetContainerInterfaceInfo", 232 Args: []interface{}{names.NewMachineTag("1-lxc-0")}, 233 }}) 234 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 235 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 236 s.assertInstances(c, lxc) 237 s.assertDefaultNetworkConfig(c, lxc) 238 s.assertDefaultStorageConfig(c, lxc) 239 } 240 241 func (s *lxcBrokerSuite) TestMaintainInstanceAddressAllocationDisabled(c *gc.C) { 242 machineId := "1/lxc/0" 243 lxc := s.startInstance(c, machineId, nil) 244 s.api.ResetCalls() 245 246 s.maintainInstance(c, machineId, nil) 247 s.api.CheckCalls(c, []gitjujutesting.StubCall{}) 248 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 249 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 250 s.assertInstances(c, lxc) 251 s.assertDefaultNetworkConfig(c, lxc) 252 s.assertDefaultStorageConfig(c, lxc) 253 } 254 255 func (s *lxcBrokerSuite) TestStartInstanceWithStorage(c *gc.C) { 256 s.api.fakeContainerConfig.AllowLXCLoopMounts = true 257 s.SetFeatureFlags(feature.AddressAllocation) 258 259 machineId := "1/lxc/0" 260 lxc := s.startInstance(c, machineId, []storage.VolumeParams{{Provider: provider.LoopProviderType}}) 261 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 262 FuncName: "ContainerConfig", 263 }, { 264 FuncName: "PrepareContainerInterfaceInfo", 265 Args: []interface{}{names.NewMachineTag("1-lxc-0")}, 266 }}) 267 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 268 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 269 s.assertInstances(c, lxc) 270 // Check storage config. 271 config := filepath.Join(s.LxcDir, string(lxc.Id()), "config") 272 AssertFileContents(c, jc.Contains, config, "lxc.aa_profile = lxc-container-default-with-mounting") 273 } 274 275 func (s *lxcBrokerSuite) TestStartInstanceLoopMountsDisallowed(c *gc.C) { 276 s.api.fakeContainerConfig.AllowLXCLoopMounts = false 277 s.SetFeatureFlags(feature.AddressAllocation) 278 machineId := "1/lxc/0" 279 lxc := s.startInstance(c, machineId, []storage.VolumeParams{{Provider: provider.LoopProviderType}}) 280 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 281 FuncName: "ContainerConfig", 282 }, { 283 FuncName: "PrepareContainerInterfaceInfo", 284 Args: []interface{}{names.NewMachineTag("1-lxc-0")}, 285 }}) 286 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 287 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 288 s.assertInstances(c, lxc) 289 s.assertDefaultStorageConfig(c, lxc) 290 } 291 292 func (s *lxcBrokerSuite) TestStartInstanceHostArch(c *gc.C) { 293 instanceConfig := s.instanceConfig(c, "1/lxc/0") 294 295 // Patch the host's arch, so the LXC broker will filter tools. 296 s.PatchValue(&arch.HostArch, func() string { return arch.PPC64EL }) 297 possibleTools := coretools.List{&coretools.Tools{ 298 Version: version.MustParseBinary("2.3.4-quantal-amd64"), 299 URL: "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz", 300 }, { 301 Version: version.MustParseBinary("2.3.4-quantal-ppc64el"), 302 URL: "http://tools.testing.invalid/2.3.4-quantal-ppc64el.tgz", 303 }} 304 callback := func(settableStatus status.Status, info string, data map[string]interface{}) error { 305 return nil 306 } 307 308 _, err := s.broker.StartInstance(environs.StartInstanceParams{ 309 Constraints: constraints.Value{}, 310 Tools: possibleTools, 311 InstanceConfig: instanceConfig, 312 StatusCallback: callback, 313 }) 314 c.Assert(err, jc.ErrorIsNil) 315 c.Assert(instanceConfig.AgentVersion().Arch, gc.Equals, arch.PPC64EL) 316 } 317 318 func (s *lxcBrokerSuite) TestStartInstanceToolsArchNotFound(c *gc.C) { 319 instanceConfig := s.instanceConfig(c, "1/lxc/0") 320 321 // Patch the host's arch, so the LXC broker will filter tools. 322 s.PatchValue(&arch.HostArch, func() string { return arch.PPC64EL }) 323 possibleTools := coretools.List{&coretools.Tools{ 324 Version: version.MustParseBinary("2.3.4-quantal-amd64"), 325 URL: "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz", 326 }} 327 callback := func(settableStatus status.Status, info string, data map[string]interface{}) error { 328 return nil 329 } 330 331 _, err := s.broker.StartInstance(environs.StartInstanceParams{ 332 Constraints: constraints.Value{}, 333 Tools: possibleTools, 334 InstanceConfig: instanceConfig, 335 StatusCallback: callback, 336 }) 337 c.Assert(err, gc.ErrorMatches, "need tools for arch ppc64el, only found \\[amd64\\]") 338 } 339 340 func (s *lxcBrokerSuite) TestStartInstanceWithBridgeEnviron(c *gc.C) { 341 s.agentConfig.SetValue(agent.LxcBridge, "br0") 342 machineId := "1/lxc/0" 343 lxc := s.startInstance(c, machineId, nil) 344 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 345 FuncName: "ContainerConfig", 346 }, { 347 FuncName: "PrepareContainerInterfaceInfo", 348 Args: []interface{}{names.NewMachineTag("1-lxc-0")}, 349 }}) 350 c.Assert(lxc.Id(), gc.Equals, instance.Id("juju-machine-1-lxc-0")) 351 c.Assert(s.lxcContainerDir(lxc), jc.IsDirectory) 352 s.assertInstances(c, lxc) 353 // Uses default network config 354 lxc_conf := filepath.Join(s.ContainerDir, string(lxc.Id()), "lxc.conf") 355 expect := []string{ 356 "lxc.network.type = veth", 357 "lxc.network.link = br0", 358 } 359 AssertFileContains(c, lxc_conf, expect...) 360 } 361 362 func (s *lxcBrokerSuite) startInstancePopulatesNetworkInfo(c *gc.C) (*environs.StartInstanceResult, error) { 363 s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { 364 return []net.Addr{&fakeAddr{"0.1.2.1/24"}}, nil 365 }) 366 fakeResolvConf := filepath.Join(c.MkDir(), "resolv.conf") 367 err := ioutil.WriteFile(fakeResolvConf, []byte("nameserver ns1.dummy\nnameserver ns2.dummy\nsearch dummy\n"), 0644) 368 c.Assert(err, jc.ErrorIsNil) 369 s.PatchValue(provisioner.ResolvConf, fakeResolvConf) 370 371 instanceConfig := s.instanceConfig(c, "42") 372 possibleTools := coretools.List{&coretools.Tools{ 373 Version: version.MustParseBinary("2.3.4-quantal-amd64"), 374 URL: "http://tools.testing.invalid/2.3.4-quantal-amd64.tgz", 375 }} 376 callback := func(settableStatus status.Status, info string, data map[string]interface{}) error { 377 return nil 378 } 379 380 return s.broker.StartInstance(environs.StartInstanceParams{ 381 Constraints: constraints.Value{}, 382 Tools: possibleTools, 383 InstanceConfig: instanceConfig, 384 StatusCallback: callback, 385 }) 386 } 387 388 func (s *lxcBrokerSuite) TestStartInstancePopulatesNetworkInfoWithAddressAllocation(c *gc.C) { 389 s.SetFeatureFlags(feature.AddressAllocation) 390 result, err := s.startInstancePopulatesNetworkInfo(c) 391 c.Assert(err, jc.ErrorIsNil) 392 c.Assert(result.NetworkInfo, gc.HasLen, 1) 393 iface := result.NetworkInfo[0] 394 c.Assert(err, jc.ErrorIsNil) 395 c.Assert(iface, jc.DeepEquals, network.InterfaceInfo{ 396 DeviceIndex: 0, 397 CIDR: "0.1.2.0/24", 398 ConfigType: network.ConfigStatic, 399 InterfaceName: "eth0", // generated from the device index. 400 DNSServers: network.NewAddresses("ns1.dummy", "ns2.dummy"), 401 DNSSearchDomains: []string{"dummy"}, 402 MACAddress: "aa:bb:cc:dd:ee:ff", 403 Address: network.NewAddress("0.1.2.3"), 404 GatewayAddress: network.NewAddress("0.1.2.1"), 405 }) 406 } 407 408 func (s *lxcBrokerSuite) TestStartInstancePopulatesNetworkInfoWithoutAddressAllocation(c *gc.C) { 409 s.SetFeatureFlags() 410 result, err := s.startInstancePopulatesNetworkInfo(c) 411 c.Assert(err, jc.ErrorIsNil) 412 c.Assert(result.NetworkInfo, gc.HasLen, 1) 413 iface := result.NetworkInfo[0] 414 c.Assert(err, jc.ErrorIsNil) 415 c.Assert(iface, jc.DeepEquals, network.InterfaceInfo{ 416 DeviceIndex: 0, 417 CIDR: "0.1.2.0/24", 418 InterfaceName: "dummy0", // generated from the device index. 419 DNSServers: network.NewAddresses("ns1.dummy", "ns2.dummy"), 420 DNSSearchDomains: []string{"dummy"}, 421 MACAddress: "aa:bb:cc:dd:ee:ff", 422 Address: network.NewAddress("0.1.2.3"), 423 GatewayAddress: network.NewAddress("0.1.2.1"), 424 }) 425 } 426 427 func (s *lxcBrokerSuite) TestStopInstance(c *gc.C) { 428 lxc0 := s.startInstance(c, "1/lxc/0", nil) 429 lxc1 := s.startInstance(c, "1/lxc/1", nil) 430 lxc2 := s.startInstance(c, "1/lxc/2", nil) 431 432 s.assertInstances(c, lxc0, lxc1, lxc2) 433 err := s.broker.StopInstances(lxc0.Id()) 434 c.Assert(err, jc.ErrorIsNil) 435 s.assertInstances(c, lxc1, lxc2) 436 c.Assert(s.lxcContainerDir(lxc0), jc.DoesNotExist) 437 c.Assert(s.lxcRemovedContainerDir(lxc0), jc.IsDirectory) 438 439 err = s.broker.StopInstances(lxc1.Id(), lxc2.Id()) 440 c.Assert(err, jc.ErrorIsNil) 441 s.assertInstances(c) 442 } 443 444 func (s *lxcBrokerSuite) TestAllInstances(c *gc.C) { 445 lxc0 := s.startInstance(c, "1/lxc/0", nil) 446 lxc1 := s.startInstance(c, "1/lxc/1", nil) 447 s.assertInstances(c, lxc0, lxc1) 448 449 err := s.broker.StopInstances(lxc1.Id()) 450 c.Assert(err, jc.ErrorIsNil) 451 lxc2 := s.startInstance(c, "1/lxc/2", nil) 452 s.assertInstances(c, lxc0, lxc2) 453 } 454 455 func (s *lxcBrokerSuite) assertInstances(c *gc.C, inst ...instance.Instance) { 456 results, err := s.broker.AllInstances() 457 c.Assert(err, jc.ErrorIsNil) 458 instancetest.MatchInstances(c, results, inst...) 459 } 460 461 func (s *lxcBrokerSuite) lxcContainerDir(inst instance.Instance) string { 462 return filepath.Join(s.ContainerDir, string(inst.Id())) 463 } 464 465 func (s *lxcBrokerSuite) lxcRemovedContainerDir(inst instance.Instance) string { 466 return filepath.Join(s.RemovedDir, string(inst.Id())) 467 } 468 469 func (s *lxcBrokerSuite) TestLocalDNSServers(c *gc.C) { 470 fakeConf := filepath.Join(c.MkDir(), "resolv.conf") 471 s.PatchValue(provisioner.ResolvConf, fakeConf) 472 473 // If config is missing, that's OK. 474 dnses, dnsSearch, err := provisioner.LocalDNSServers() 475 c.Assert(err, jc.ErrorIsNil) 476 c.Assert(dnses, gc.HasLen, 0) 477 c.Assert(dnsSearch, gc.Equals, "") 478 479 // Enter some data in fakeConf. 480 data := ` 481 anything else is ignored 482 # comments are ignored 483 nameserver 0.1.2.3 # that's parsed 484 search foo.baz # comment ignored 485 # nameserver 42.42.42.42 - ignored as well 486 nameserver 8.8.8.8 487 nameserver example.com # comment after is ok 488 ` 489 err = ioutil.WriteFile(fakeConf, []byte(data), 0644) 490 c.Assert(err, jc.ErrorIsNil) 491 492 dnses, dnsSearch, err = provisioner.LocalDNSServers() 493 c.Assert(err, jc.ErrorIsNil) 494 c.Assert(dnses, jc.DeepEquals, network.NewAddresses( 495 "0.1.2.3", "8.8.8.8", "example.com", 496 )) 497 c.Assert(dnsSearch, gc.Equals, "foo.baz") 498 } 499 500 func (s *lxcBrokerSuite) TestMustParseTemplate(c *gc.C) { 501 f := func() { provisioner.MustParseTemplate("", "{{invalid}") } 502 c.Assert(f, gc.PanicMatches, `template: :1: function "invalid" not defined`) 503 504 tmpl := provisioner.MustParseTemplate("name", "X={{.X}}") 505 c.Assert(tmpl, gc.NotNil) 506 c.Assert(tmpl.Name(), gc.Equals, "name") 507 508 var buf bytes.Buffer 509 err := tmpl.Execute(&buf, struct{ X string }{"42"}) 510 c.Assert(err, jc.ErrorIsNil) 511 c.Assert(buf.String(), gc.Equals, "X=42") 512 } 513 514 func (s *lxcBrokerSuite) TestRunTemplateCommand(c *gc.C) { 515 for i, test := range []struct { 516 source string 517 exitNonZeroOK bool 518 data interface{} 519 exitCode int 520 expectErr string 521 }{{ 522 source: "echo {{.Name}}", 523 exitNonZeroOK: false, 524 data: struct{ Name string }{"foo"}, 525 exitCode: 0, 526 }, { 527 source: "exit {{.Code}}", 528 exitNonZeroOK: false, 529 data: struct{ Code int }{123}, 530 exitCode: 123, 531 expectErr: `command "exit 123" failed with exit code 123`, 532 }, { 533 source: "exit {{.Code}}", 534 exitNonZeroOK: true, 535 data: struct{ Code int }{56}, 536 exitCode: 56, 537 }, { 538 source: "exit 42", 539 exitNonZeroOK: true, 540 exitCode: 42, 541 }, { 542 source: "some-invalid-command", 543 exitNonZeroOK: false, 544 exitCode: 127, // returned by bash. 545 expectErr: `command "some-invalid-command" failed with exit code 127`, 546 }} { 547 c.Logf("test %d: %q -> %d", i, test.source, test.exitCode) 548 t, err := template.New(fmt.Sprintf("test %d", i)).Parse(test.source) 549 if !c.Check(err, jc.ErrorIsNil, gc.Commentf("parsing %q", test.source)) { 550 continue 551 } 552 exitCode, err := provisioner.RunTemplateCommand(t, test.exitNonZeroOK, test.data) 553 if test.expectErr != "" { 554 c.Check(err, gc.ErrorMatches, test.expectErr) 555 } else { 556 c.Check(err, jc.ErrorIsNil) 557 } 558 c.Check(exitCode, gc.Equals, test.exitCode) 559 } 560 } 561 562 func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesInvalidArgs(c *gc.C) { 563 // Isolate the test from the host machine. 564 gitjujutesting.PatchExecutableThrowError(c, s, "iptables", 42) 565 gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123) 566 567 // Check that all the arguments are verified to be non-empty. 568 expectStartupErr := "primaryNIC, primaryAddr, bridgeName, and ifaceInfo must be all set" 569 emptyIfaceInfo := []network.InterfaceInfo{} 570 for i, test := range []struct { 571 about string 572 primaryNIC string 573 primaryAddr network.Address 574 bridgeName string 575 ifaceInfo []network.InterfaceInfo 576 expectErr string 577 }{{ 578 about: "all empty", 579 primaryNIC: "", 580 primaryAddr: network.Address{}, 581 bridgeName: "", 582 ifaceInfo: nil, 583 expectErr: expectStartupErr, 584 }, { 585 about: "all but primaryNIC empty", 586 primaryNIC: "nic", 587 primaryAddr: network.Address{}, 588 bridgeName: "", 589 ifaceInfo: nil, 590 expectErr: expectStartupErr, 591 }, { 592 about: "all but primaryAddr empty", 593 primaryNIC: "", 594 primaryAddr: network.NewAddress("0.1.2.1"), 595 bridgeName: "", 596 ifaceInfo: nil, 597 expectErr: expectStartupErr, 598 }, { 599 about: "all but bridgeName empty", 600 primaryNIC: "", 601 primaryAddr: network.Address{}, 602 bridgeName: "bridge", 603 ifaceInfo: nil, 604 expectErr: expectStartupErr, 605 }, { 606 about: "all but primaryNIC and bridgeName empty", 607 primaryNIC: "nic", 608 primaryAddr: network.Address{}, 609 bridgeName: "bridge", 610 ifaceInfo: nil, 611 expectErr: expectStartupErr, 612 }, { 613 about: "all but primaryNIC and primaryAddr empty", 614 primaryNIC: "nic", 615 primaryAddr: network.NewAddress("0.1.2.1"), 616 bridgeName: "", 617 ifaceInfo: nil, 618 expectErr: expectStartupErr, 619 }, { 620 about: "all but primaryAddr and bridgeName empty", 621 primaryNIC: "", 622 primaryAddr: network.NewAddress("0.1.2.1"), 623 bridgeName: "bridge", 624 ifaceInfo: nil, 625 expectErr: expectStartupErr, 626 }, { 627 about: "all set except ifaceInfo", 628 primaryNIC: "nic", 629 primaryAddr: network.NewAddress("0.1.2.1"), 630 bridgeName: "bridge", 631 ifaceInfo: nil, 632 expectErr: expectStartupErr, 633 }, { 634 about: "all empty (ifaceInfo set but empty)", 635 primaryNIC: "", 636 primaryAddr: network.Address{}, 637 bridgeName: "", 638 ifaceInfo: emptyIfaceInfo, 639 expectErr: expectStartupErr, 640 }, { 641 about: "all but primaryNIC empty (ifaceInfo set but empty)", 642 primaryNIC: "nic", 643 primaryAddr: network.Address{}, 644 bridgeName: "", 645 ifaceInfo: emptyIfaceInfo, 646 expectErr: expectStartupErr, 647 }, { 648 about: "all but primaryAddr empty (ifaceInfo set but empty)", 649 primaryNIC: "", 650 primaryAddr: network.NewAddress("0.1.2.1"), 651 bridgeName: "", 652 ifaceInfo: emptyIfaceInfo, 653 expectErr: expectStartupErr, 654 }, { 655 about: "all but bridgeName empty (ifaceInfo set but empty)", 656 primaryNIC: "", 657 primaryAddr: network.Address{}, 658 bridgeName: "bridge", 659 ifaceInfo: emptyIfaceInfo, 660 expectErr: expectStartupErr, 661 }, { 662 about: "just primaryAddr is empty and ifaceInfo set but empty", 663 primaryNIC: "nic", 664 primaryAddr: network.Address{}, 665 bridgeName: "bridge", 666 ifaceInfo: emptyIfaceInfo, 667 expectErr: expectStartupErr, 668 }, { 669 about: "just bridgeName is empty and ifaceInfo set but empty", 670 primaryNIC: "nic", 671 primaryAddr: network.NewAddress("0.1.2.1"), 672 bridgeName: "", 673 ifaceInfo: emptyIfaceInfo, 674 expectErr: expectStartupErr, 675 }, { 676 about: "just primaryNIC is empty and ifaceInfo set but empty", 677 primaryNIC: "", 678 primaryAddr: network.NewAddress("0.1.2.1"), 679 bridgeName: "bridge", 680 ifaceInfo: emptyIfaceInfo, 681 expectErr: expectStartupErr, 682 }, { 683 about: "all set except ifaceInfo, which is set but empty", 684 primaryNIC: "nic", 685 primaryAddr: network.NewAddress("0.1.2.1"), 686 bridgeName: "bridge", 687 ifaceInfo: emptyIfaceInfo, 688 expectErr: expectStartupErr, 689 }, { 690 about: "all set, but ifaceInfo has empty Address", 691 primaryNIC: "nic", 692 primaryAddr: network.NewAddress("0.1.2.1"), 693 bridgeName: "bridge", 694 // No Address set. 695 ifaceInfo: []network.InterfaceInfo{{DeviceIndex: 0}}, 696 expectErr: `container IP "" must be set`, 697 }} { 698 c.Logf("test %d: %s", i, test.about) 699 err := provisioner.SetupRoutesAndIPTables( 700 test.primaryNIC, 701 test.primaryAddr, 702 test.bridgeName, 703 test.ifaceInfo, 704 false, // TODO(dimitern): Untested. 705 ) 706 c.Check(err, gc.ErrorMatches, test.expectErr) 707 } 708 } 709 710 func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesIPTablesCheckError(c *gc.C) { 711 // Isolate the test from the host machine. 712 gitjujutesting.PatchExecutableThrowError(c, s, "iptables", 42) 713 gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123) 714 715 ifaceInfo := []network.InterfaceInfo{{ 716 Address: network.NewAddress("0.1.2.3"), 717 }} 718 719 addr := network.NewAddress("0.1.2.1") 720 err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false) 721 c.Assert(err, gc.ErrorMatches, "iptables failed with unexpected exit code 42") 722 } 723 724 func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesIPTablesAddError(c *gc.C) { 725 // Isolate the test from the host machine. Patch iptables with a 726 // script which returns code=1 for the check but fails when adding 727 // the rule. 728 script := `if [[ "$3" == "-C" ]]; then exit 1; else exit 42; fi` 729 gitjujutesting.PatchExecutable(c, s, "iptables", script) 730 gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123) 731 732 fakeptablesRules := map[string]provisioner.IptablesRule{ 733 "IPTablesSNAT": { 734 "nat", 735 "POSTROUTING", 736 "{{.HostIF}} {{.HostIP}}", 737 }, 738 } 739 s.PatchValue(provisioner.IptablesRules, fakeptablesRules) 740 741 ifaceInfo := []network.InterfaceInfo{{ 742 Address: network.NewAddress("0.1.2.3"), 743 }} 744 745 addr := network.NewAddress("0.1.2.1") 746 err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false) 747 c.Assert(err, gc.ErrorMatches, `command "iptables -t nat -I .*" failed with exit code 42`) 748 } 749 750 func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesIPRouteError(c *gc.C) { 751 // Isolate the test from the host machine. 752 // Returning code=0 from iptables means we won't add a rule. 753 gitjujutesting.PatchExecutableThrowError(c, s, "iptables", 0) 754 gitjujutesting.PatchExecutableThrowError(c, s, "ip", 123) 755 756 ifaceInfo := []network.InterfaceInfo{{ 757 Address: network.NewAddress("0.1.2.3"), 758 }} 759 760 addr := network.NewAddress("0.1.2.1") 761 err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false) 762 c.Assert(err, gc.ErrorMatches, 763 `command "ip route add 0.1.2.3 dev bridge" failed with exit code 123`, 764 ) 765 } 766 767 func (s *lxcBrokerSuite) TestSetupRoutesAndIPTablesAddsRuleIfMissing(c *gc.C) { 768 // Isolate the test from the host machine. Because PatchExecutable 769 // does not allow us to assert on subsequent executions of the 770 // same binary, we need to replace the iptables commands with 771 // separate ones. The check returns code=1 to trigger calling 772 // add. 773 fakeptablesRules := map[string]provisioner.IptablesRule{ 774 "IPTablesSNAT": { 775 "nat", 776 "POSTROUTING", 777 "{{.HostIF}} {{.HostIP}}", 778 }, 779 } 780 s.PatchValue(provisioner.IptablesRules, fakeptablesRules) 781 782 gitjujutesting.PatchExecutableAsEchoArgs(c, s, "iptables", 1, 0) 783 gitjujutesting.PatchExecutableAsEchoArgs(c, s, "ip") 784 785 ifaceInfo := []network.InterfaceInfo{{ 786 Address: network.NewAddress("0.1.2.3"), 787 }} 788 789 addr := network.NewAddress("0.1.2.1") 790 err := provisioner.SetupRoutesAndIPTables("nic", addr, "bridge", ifaceInfo, false) 791 c.Assert(err, jc.ErrorIsNil) 792 793 // Now verify the expected commands - since check returns 1, add 794 // will be called before ip route add. 795 796 gitjujutesting.AssertEchoArgs(c, "iptables", "-t", "nat", "-C", "POSTROUTING", "nic", "0.1.2.1") 797 gitjujutesting.AssertEchoArgs(c, "iptables", "-t", "nat", "-I", "POSTROUTING", "1", "nic", "0.1.2.1") 798 gitjujutesting.AssertEchoArgs(c, "ip", "route", "add", "0.1.2.3", "dev", "bridge") 799 } 800 801 func (s *lxcBrokerSuite) patchNetInterfaceByName(c *gc.C, interfaceName string) { 802 s.PatchValue(provisioner.NetInterfaceByName, func(name string) (*net.Interface, error) { 803 if interfaceName != name { 804 return nil, errors.New("no such network interface") 805 } 806 return &net.Interface{ 807 Index: 0, 808 Name: name, 809 Flags: net.FlagUp, 810 }, nil 811 }) 812 } 813 814 func (s *lxcBrokerSuite) patchNetInterfaceByNameAddrs(c *gc.C, interfaceName string, fakeAddrs ...string) { 815 addrs := make([]net.Addr, len(fakeAddrs)) 816 817 for i, a := range fakeAddrs { 818 addrs[i] = &fakeAddr{a} 819 } 820 821 s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { 822 c.Assert(i.Name, gc.Matches, interfaceName) 823 return addrs, nil 824 }) 825 } 826 827 func (s *lxcBrokerSuite) TestDiscoverPrimaryNICNetInterfacesError(c *gc.C) { 828 s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) { 829 return nil, errors.New("boom!") 830 }) 831 832 nic, addr, err := provisioner.DiscoverPrimaryNIC() 833 c.Assert(err, gc.ErrorMatches, "cannot get network interfaces: boom!") 834 c.Assert(nic, gc.Equals, "") 835 c.Assert(addr, jc.DeepEquals, network.Address{}) 836 } 837 838 func (s *lxcBrokerSuite) TestDiscoverPrimaryNICInterfaceAddrsError(c *gc.C) { 839 s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) { 840 return []net.Interface{{ 841 Index: 0, 842 Name: "fake", 843 Flags: net.FlagUp, 844 }}, nil 845 }) 846 s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { 847 return nil, errors.New("boom!") 848 }) 849 850 nic, addr, err := provisioner.DiscoverPrimaryNIC() 851 c.Assert(err, gc.ErrorMatches, `cannot get "fake" addresses: boom!`) 852 c.Assert(nic, gc.Equals, "") 853 c.Assert(addr, jc.DeepEquals, network.Address{}) 854 } 855 856 func (s *lxcBrokerSuite) TestDiscoverPrimaryNICInvalidAddr(c *gc.C) { 857 s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) { 858 return []net.Interface{{ 859 Index: 0, 860 Name: "fake", 861 Flags: net.FlagUp, 862 }}, nil 863 }) 864 s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { 865 return []net.Addr{&fakeAddr{}}, nil 866 }) 867 868 nic, addr, err := provisioner.DiscoverPrimaryNIC() 869 c.Assert(err, gc.ErrorMatches, `cannot parse address "fakeAddr": invalid CIDR address: fakeAddr`) 870 c.Assert(nic, gc.Equals, "") 871 c.Assert(addr, jc.DeepEquals, network.Address{}) 872 } 873 874 func (s *lxcBrokerSuite) TestDiscoverPrimaryNICInterfaceNotFound(c *gc.C) { 875 s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) { 876 return nil, nil 877 }) 878 879 nic, addr, err := provisioner.DiscoverPrimaryNIC() 880 c.Assert(err, gc.ErrorMatches, "cannot detect the primary network interface") 881 c.Assert(nic, gc.Equals, "") 882 c.Assert(addr, jc.DeepEquals, network.Address{}) 883 } 884 885 type fakeAddr struct{ value string } 886 887 func (f *fakeAddr) Network() string { return "net" } 888 func (f *fakeAddr) String() string { 889 if f.value != "" { 890 return f.value 891 } 892 return "fakeAddr" 893 } 894 895 var _ net.Addr = (*fakeAddr)(nil) 896 897 func (s *lxcBrokerSuite) TestDiscoverPrimaryNICSuccess(c *gc.C) { 898 s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) { 899 return []net.Interface{{ 900 Index: 0, 901 Name: "lo", 902 Flags: net.FlagUp | net.FlagLoopback, // up but loopback - ignored. 903 }, { 904 Index: 1, 905 Name: "if0", 906 Flags: net.FlagPointToPoint, // not up - ignored. 907 }, { 908 Index: 2, 909 Name: "if1", 910 Flags: net.FlagUp, // up but no addresses - ignored. 911 }, { 912 Index: 3, 913 Name: "if2", 914 Flags: net.FlagUp, // up and has addresses - returned. 915 }}, nil 916 }) 917 s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { 918 // We should be called only for the last two NICs. The first 919 // one (if1) won't have addresses, only the last one (if2). 920 c.Assert(i, gc.NotNil) 921 c.Assert(i.Name, gc.Matches, "if[12]") 922 if i.Name == "if2" { 923 return []net.Addr{&fakeAddr{"0.1.2.3/24"}}, nil 924 } 925 // For if1 we return no addresses. 926 return nil, nil 927 }) 928 929 nic, addr, err := provisioner.DiscoverPrimaryNIC() 930 c.Assert(err, jc.ErrorIsNil) 931 c.Assert(nic, gc.Equals, "if2") 932 c.Assert(addr, jc.DeepEquals, network.NewAddress("0.1.2.3")) 933 } 934 935 func (s *lxcBrokerSuite) TestConfigureContainerNetwork(c *gc.C) { 936 // All the pieces used by this func are separately tested, we just 937 // test the integration between them. 938 s.PatchValue(provisioner.NetInterfaces, func() ([]net.Interface, error) { 939 return []net.Interface{{ 940 Index: 0, 941 Name: "fake0", 942 Flags: net.FlagUp, 943 }}, nil 944 }) 945 s.PatchValue(provisioner.InterfaceAddrs, func(i *net.Interface) ([]net.Addr, error) { 946 return []net.Addr{&fakeAddr{"0.1.2.1/24"}}, nil 947 }) 948 fakeResolvConf := filepath.Join(c.MkDir(), "resolv.conf") 949 err := ioutil.WriteFile(fakeResolvConf, []byte("nameserver ns1.dummy\n"), 0644) 950 c.Assert(err, jc.ErrorIsNil) 951 s.PatchValue(provisioner.ResolvConf, fakeResolvConf) 952 953 // When ifaceInfo is not empty it shouldn't do anything and both 954 // the error and the result are nil. 955 ifaceInfo := []network.InterfaceInfo{{DeviceIndex: 0}} 956 // First call as if we are configuring the container for the first time 957 result, err := provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, true, false) 958 c.Assert(err, jc.ErrorIsNil) 959 c.Assert(result, gc.IsNil) 960 s.api.CheckCalls(c, []gitjujutesting.StubCall{}) 961 962 // Next call as if the container has already been configured. 963 s.api.ResetCalls() 964 result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false) 965 c.Assert(err, jc.ErrorIsNil) 966 c.Assert(result, gc.IsNil) 967 s.api.CheckCalls(c, []gitjujutesting.StubCall{}) 968 969 // Call as if the container already has a network configuration, but doesn't. 970 s.api.ResetCalls() 971 s.api.SetErrors(errors.NotProvisionedf("machine-42 has no network provisioning info")) 972 ifaceInfo = []network.InterfaceInfo{} 973 result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false) 974 c.Assert(err, gc.ErrorMatches, "machine-42 has no network provisioning info not provisioned") 975 c.Assert(result, jc.DeepEquals, []network.InterfaceInfo{}) 976 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 977 FuncName: "GetContainerInterfaceInfo", 978 Args: []interface{}{names.NewMachineTag("42")}, 979 }}) 980 981 // When it's not empty, result should be populated as expected. 982 s.api.ResetCalls() 983 result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false) 984 985 c.Assert(err, jc.ErrorIsNil) 986 c.Assert(result, gc.HasLen, 1) 987 c.Assert(err, jc.ErrorIsNil) 988 c.Assert(result, jc.DeepEquals, []network.InterfaceInfo{{ 989 DeviceIndex: 0, 990 CIDR: "0.1.2.0/24", 991 ConfigType: network.ConfigStatic, 992 InterfaceName: "eth0", // generated from the device index. 993 MACAddress: "aa:bb:cc:dd:ee:ff", 994 DNSServers: network.NewAddresses("ns1.dummy"), 995 DNSSearchDomains: []string{""}, 996 Address: network.NewAddress("0.1.2.3"), 997 GatewayAddress: network.NewAddress("0.1.2.1"), 998 }}) 999 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 1000 FuncName: "GetContainerInterfaceInfo", 1001 Args: []interface{}{names.NewMachineTag("42")}, 1002 }}) 1003 1004 s.api.ResetCalls() 1005 result, err = provisioner.ConfigureContainerNetwork("42", "bridge", s.api, ifaceInfo, false, false) 1006 c.Assert(result, gc.HasLen, 1) 1007 c.Assert(err, jc.ErrorIsNil) 1008 c.Assert(result, jc.DeepEquals, []network.InterfaceInfo{{ 1009 DeviceIndex: 0, 1010 CIDR: "0.1.2.0/24", 1011 ConfigType: network.ConfigStatic, 1012 InterfaceName: "eth0", // generated from the device index. 1013 MACAddress: "aa:bb:cc:dd:ee:ff", 1014 DNSServers: network.NewAddresses("ns1.dummy"), 1015 DNSSearchDomains: []string{""}, 1016 Address: network.NewAddress("0.1.2.3"), 1017 GatewayAddress: network.NewAddress("0.1.2.1"), 1018 }}) 1019 1020 s.api.CheckCalls(c, []gitjujutesting.StubCall{{ 1021 FuncName: "GetContainerInterfaceInfo", 1022 Args: []interface{}{names.NewMachineTag("42")}, 1023 }}) 1024 } 1025 1026 type lxcProvisionerSuite struct { 1027 CommonProvisionerSuite 1028 lxcSuite 1029 events chan mock.Event 1030 } 1031 1032 var _ = gc.Suite(&lxcProvisionerSuite{}) 1033 1034 func (s *lxcProvisionerSuite) SetUpSuite(c *gc.C) { 1035 if runtime.GOOS == "windows" { 1036 c.Skip("Skipping lxc tests on windows") 1037 } 1038 s.CommonProvisionerSuite.SetUpSuite(c) 1039 s.lxcSuite.SetUpSuite(c) 1040 } 1041 1042 func (s *lxcProvisionerSuite) TearDownSuite(c *gc.C) { 1043 s.lxcSuite.TearDownSuite(c) 1044 s.CommonProvisionerSuite.TearDownSuite(c) 1045 } 1046 1047 func (s *lxcProvisionerSuite) SetUpTest(c *gc.C) { 1048 s.CommonProvisionerSuite.SetUpTest(c) 1049 s.lxcSuite.SetUpTest(c) 1050 1051 s.events = make(chan mock.Event, 25) 1052 s.ContainerFactory.AddListener(s.events) 1053 } 1054 1055 func (s *lxcProvisionerSuite) expectStarted(c *gc.C, machine *state.Machine) string { 1056 // This check in particular leads to tests just hanging 1057 // indefinitely quite often on i386. 1058 coretesting.SkipIfI386(c, "lp:1425569") 1059 1060 var event mock.Event 1061 s.State.StartSync() 1062 select { 1063 case event = <-s.events: 1064 c.Assert(event.Action, gc.Equals, mock.Created) 1065 argsSet := set.NewStrings(event.TemplateArgs...) 1066 c.Assert(argsSet.Contains("imageURL"), jc.IsTrue) 1067 case <-time.After(coretesting.LongWait): 1068 c.Fatalf("timeout while waiting the mock container to get created") 1069 } 1070 1071 select { 1072 case event = <-s.events: 1073 c.Assert(event.Action, gc.Equals, mock.Started) 1074 err := machine.Refresh() 1075 c.Assert(err, jc.ErrorIsNil) 1076 case <-time.After(coretesting.LongWait): 1077 c.Fatalf("timeout while waiting the mock container to start") 1078 } 1079 1080 s.waitInstanceId(c, machine, instance.Id(event.InstanceId)) 1081 return event.InstanceId 1082 } 1083 1084 func (s *lxcProvisionerSuite) expectStopped(c *gc.C, instId string) { 1085 // This check in particular leads to tests just hanging 1086 // indefinitely quite often on i386. 1087 coretesting.SkipIfI386(c, "lp:1425569") 1088 1089 s.State.StartSync() 1090 select { 1091 case event := <-s.events: 1092 c.Assert(event.Action, gc.Equals, mock.Stopped) 1093 case <-time.After(coretesting.LongWait): 1094 c.Fatalf("timeout while waiting the mock container to stop") 1095 } 1096 1097 select { 1098 case event := <-s.events: 1099 c.Assert(event.Action, gc.Equals, mock.Destroyed) 1100 c.Assert(event.InstanceId, gc.Equals, instId) 1101 case <-time.After(coretesting.LongWait): 1102 c.Fatalf("timeout while waiting the mock container to get destroyed") 1103 } 1104 } 1105 1106 func (s *lxcProvisionerSuite) expectNoEvents(c *gc.C) { 1107 select { 1108 case event := <-s.events: 1109 c.Fatalf("unexpected event %#v", event) 1110 case <-time.After(coretesting.ShortWait): 1111 return 1112 } 1113 } 1114 1115 func (s *lxcProvisionerSuite) TearDownTest(c *gc.C) { 1116 close(s.events) 1117 s.lxcSuite.TearDownTest(c) 1118 s.CommonProvisionerSuite.TearDownTest(c) 1119 } 1120 1121 func (s *lxcProvisionerSuite) newLxcProvisioner(c *gc.C) provisioner.Provisioner { 1122 parentMachineTag := names.NewMachineTag("0") 1123 agentConfig := s.AgentConfigForTag(c, parentMachineTag) 1124 managerConfig := container.ManagerConfig{ 1125 container.ConfigName: "juju", 1126 "log-dir": c.MkDir(), 1127 "use-clone": "false", 1128 } 1129 broker, err := provisioner.NewLxcBroker(s.provisioner, agentConfig, managerConfig, &containertesting.MockURLGetter{}, false, 0) 1130 c.Assert(err, jc.ErrorIsNil) 1131 toolsFinder := (*provisioner.GetToolsFinder)(s.provisioner) 1132 w, err := provisioner.NewContainerProvisioner(instance.LXC, s.provisioner, agentConfig, broker, toolsFinder) 1133 c.Assert(err, jc.ErrorIsNil) 1134 return w 1135 } 1136 1137 func (s *lxcProvisionerSuite) TestProvisionerStartStop(c *gc.C) { 1138 p := s.newLxcProvisioner(c) 1139 stop(c, p) 1140 } 1141 1142 func (s *lxcProvisionerSuite) TestDoesNotStartEnvironMachines(c *gc.C) { 1143 p := s.newLxcProvisioner(c) 1144 defer stop(c, p) 1145 1146 // Check that an instance is not provisioned when the machine is created. 1147 _, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits) 1148 c.Assert(err, jc.ErrorIsNil) 1149 1150 s.expectNoEvents(c) 1151 } 1152 1153 func (s *lxcProvisionerSuite) TestDoesNotHaveRetryWatcher(c *gc.C) { 1154 p := s.newLxcProvisioner(c) 1155 defer stop(c, p) 1156 1157 w, err := provisioner.GetRetryWatcher(p) 1158 c.Assert(w, gc.IsNil) 1159 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 1160 } 1161 1162 func (s *lxcProvisionerSuite) addContainer(c *gc.C) *state.Machine { 1163 template := state.MachineTemplate{ 1164 Series: series.LatestLts(), 1165 Jobs: []state.MachineJob{state.JobHostUnits}, 1166 } 1167 container, err := s.State.AddMachineInsideMachine(template, "0", instance.LXC) 1168 c.Assert(err, jc.ErrorIsNil) 1169 return container 1170 } 1171 1172 func (s *lxcProvisionerSuite) maybeUploadTools(c *gc.C) { 1173 // The default series tools are already uploaded 1174 // for amd64 in the base suite. 1175 if arch.HostArch() == arch.AMD64 { 1176 return 1177 } 1178 1179 storageDir := c.MkDir() 1180 s.CommonProvisionerSuite.PatchValue(&tools.DefaultBaseURL, storageDir) 1181 stor, err := filestorage.NewFileStorageWriter(storageDir) 1182 c.Assert(err, jc.ErrorIsNil) 1183 1184 defaultTools := version.Binary{ 1185 Number: jujuversion.Current, 1186 Arch: arch.HostArch(), 1187 Series: series.LatestLts(), 1188 } 1189 1190 envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", defaultTools) 1191 envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", defaultTools) 1192 } 1193 1194 func (s *lxcProvisionerSuite) TestContainerStartedAndStopped(c *gc.C) { 1195 coretesting.SkipIfI386(c, "lp:1425569") 1196 s.maybeUploadTools(c) 1197 1198 p := s.newLxcProvisioner(c) 1199 defer stop(c, p) 1200 1201 container := s.addContainer(c) 1202 name := "juju-" + container.Tag().String() 1203 containertesting.EnsureLXCRootFSEtcNetwork(c, name) 1204 instId := s.expectStarted(c, container) 1205 1206 // ...and removed, along with the machine, when the machine is Dead. 1207 c.Assert(container.EnsureDead(), gc.IsNil) 1208 s.expectStopped(c, instId) 1209 s.waitRemoved(c, container) 1210 } 1211 1212 func (s *lxcProvisionerSuite) TestLXCProvisionerObservesConfigChanges(c *gc.C) { 1213 p := s.newLxcProvisioner(c) 1214 defer stop(c, p) 1215 s.assertProvisionerObservesConfigChanges(c, p) 1216 } 1217 1218 type fakeAPI struct { 1219 *gitjujutesting.Stub 1220 1221 fakeContainerConfig params.ContainerConfig 1222 fakeInterfaceInfo network.InterfaceInfo 1223 } 1224 1225 var _ provisioner.APICalls = (*fakeAPI)(nil) 1226 1227 var fakeInterfaceInfo network.InterfaceInfo = network.InterfaceInfo{ 1228 DeviceIndex: 0, 1229 MACAddress: "aa:bb:cc:dd:ee:ff", 1230 CIDR: "0.1.2.0/24", 1231 InterfaceName: "dummy0", 1232 Address: network.NewAddress("0.1.2.3"), 1233 GatewayAddress: network.NewAddress("0.1.2.1"), 1234 } 1235 1236 var fakeContainerConfig = params.ContainerConfig{ 1237 UpdateBehavior: ¶ms.UpdateBehavior{true, true}, 1238 ProviderType: "fake", 1239 AuthorizedKeys: coretesting.FakeAuthKeys, 1240 SSLHostnameVerification: true, 1241 } 1242 1243 func NewFakeAPI() *fakeAPI { 1244 return &fakeAPI{ 1245 Stub: &gitjujutesting.Stub{}, 1246 fakeContainerConfig: fakeContainerConfig, 1247 fakeInterfaceInfo: fakeInterfaceInfo, 1248 } 1249 } 1250 1251 func (f *fakeAPI) ContainerConfig() (params.ContainerConfig, error) { 1252 f.MethodCall(f, "ContainerConfig") 1253 if err := f.NextErr(); err != nil { 1254 return params.ContainerConfig{}, err 1255 } 1256 return f.fakeContainerConfig, nil 1257 } 1258 1259 func (f *fakeAPI) PrepareContainerInterfaceInfo(tag names.MachineTag) ([]network.InterfaceInfo, error) { 1260 f.MethodCall(f, "PrepareContainerInterfaceInfo", tag) 1261 if err := f.NextErr(); err != nil { 1262 return nil, err 1263 } 1264 return []network.InterfaceInfo{f.fakeInterfaceInfo}, nil 1265 } 1266 1267 func (f *fakeAPI) GetContainerInterfaceInfo(tag names.MachineTag) ([]network.InterfaceInfo, error) { 1268 f.MethodCall(f, "GetContainerInterfaceInfo", tag) 1269 if err := f.NextErr(); err != nil { 1270 return nil, err 1271 } 1272 return []network.InterfaceInfo{f.fakeInterfaceInfo}, nil 1273 } 1274 1275 func (f *fakeAPI) ReleaseContainerAddresses(tag names.MachineTag) error { 1276 f.MethodCall(f, "ReleaseContainerAddresses", tag) 1277 if err := f.NextErr(); err != nil { 1278 return err 1279 } 1280 return nil 1281 }