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