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