github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cloudconfig/cloudinit/network_ubuntu_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package cloudinit_test 6 7 import ( 8 "crypto/rand" 9 "encoding/hex" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "runtime" 15 "strings" 16 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils/exec" 19 gc "gopkg.in/check.v1" 20 "gopkg.in/yaml.v2" 21 22 "github.com/juju/juju/cloudconfig/cloudinit" 23 "github.com/juju/juju/container" 24 "github.com/juju/juju/network" 25 "github.com/juju/juju/testing" 26 ) 27 28 type NetworkUbuntuSuite struct { 29 testing.BaseSuite 30 31 networkInterfacesPythonFile string 32 systemNetworkInterfacesFile string 33 jujuNetplanFile string 34 35 fakeInterfaces []network.InterfaceInfo 36 37 expectedSampleConfigHeader string 38 expectedSampleConfigTemplate string 39 expectedSampleConfigWriting string 40 expectedSampleUserData string 41 expectedFallbackConfig string 42 expectedBaseConfig string 43 expectedFullNetplanYaml string 44 expectedBaseNetplanYaml string 45 expectedFullNetplan string 46 expectedBaseNetplan string 47 expectedFallbackUserData string 48 tempFolder string 49 pythonVersions []string 50 originalSystemNetworkInterfaces string 51 } 52 53 var _ = gc.Suite(&NetworkUbuntuSuite{}) 54 55 func (s *NetworkUbuntuSuite) SetUpTest(c *gc.C) { 56 s.BaseSuite.SetUpTest(c) 57 58 if runtime.GOOS == "windows" { 59 c.Skip("This test is for Linux only") 60 } 61 62 s.tempFolder = c.MkDir() 63 networkFolder := c.MkDir() 64 netplanFolder := c.MkDir() 65 s.systemNetworkInterfacesFile = filepath.Join(networkFolder, "system-interfaces") 66 s.networkInterfacesPythonFile = filepath.Join(networkFolder, "system-interfaces.py") 67 s.jujuNetplanFile = filepath.Join(netplanFolder, "79-juju.yaml") 68 69 s.fakeInterfaces = []network.InterfaceInfo{{ 70 InterfaceName: "any0", 71 CIDR: "0.1.2.0/24", 72 ConfigType: network.ConfigStatic, 73 NoAutoStart: false, 74 Address: network.NewAddress("0.1.2.3"), 75 DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), 76 DNSSearchDomains: []string{"foo", "bar"}, 77 GatewayAddress: network.NewAddress("0.1.2.1"), 78 MACAddress: "aa:bb:cc:dd:ee:f0", 79 MTU: 8317, 80 }, { 81 InterfaceName: "any1", 82 CIDR: "0.2.2.0/24", 83 ConfigType: network.ConfigStatic, 84 NoAutoStart: false, 85 Address: network.NewAddress("0.2.2.4"), 86 DNSServers: network.NewAddresses("ns1.invalid", "ns2.invalid"), 87 DNSSearchDomains: []string{"foo", "bar"}, 88 GatewayAddress: network.NewAddress("0.2.2.1"), 89 MACAddress: "aa:bb:cc:dd:ee:f1", 90 Routes: []network.Route{{ 91 DestinationCIDR: "0.5.6.0/24", 92 GatewayIP: "0.2.2.1", 93 Metric: 50, 94 }}, 95 }, { 96 InterfaceName: "any2", 97 ConfigType: network.ConfigDHCP, 98 MACAddress: "aa:bb:cc:dd:ee:f2", 99 NoAutoStart: true, 100 }, { 101 InterfaceName: "any3", 102 ConfigType: network.ConfigDHCP, 103 MACAddress: "aa:bb:cc:dd:ee:f3", 104 NoAutoStart: false, 105 }, { 106 InterfaceName: "any4", 107 ConfigType: network.ConfigManual, 108 MACAddress: "aa:bb:cc:dd:ee:f4", 109 NoAutoStart: true, 110 }, { 111 InterfaceName: "any5", 112 ConfigType: network.ConfigStatic, 113 MACAddress: "aa:bb:cc:dd:ee:f5", 114 NoAutoStart: false, 115 CIDR: "2001:db8::/64", 116 Address: network.NewAddress("2001:db8::dead:beef"), 117 GatewayAddress: network.NewAddress("2001:db8::dead:f00"), 118 }} 119 120 for _, version := range []string{ 121 "/usr/bin/python2", 122 "/usr/bin/python3", 123 "/usr/bin/python", 124 } { 125 if _, err := os.Stat(version); err == nil { 126 s.pythonVersions = append(s.pythonVersions, version) 127 } 128 } 129 c.Assert(s.pythonVersions, gc.Not(gc.HasLen), 0) 130 131 s.expectedSampleConfigHeader = `#cloud-config 132 bootcmd: 133 ` 134 s.expectedSampleConfigWriting = `- install -D -m 644 /dev/null '%[1]s.templ' 135 - |- 136 printf '%%s\n' ' 137 auto lo {ethaa_bb_cc_dd_ee_f0} {ethaa_bb_cc_dd_ee_f1} {ethaa_bb_cc_dd_ee_f3} {ethaa_bb_cc_dd_ee_f5} 138 139 iface lo inet loopback 140 dns-nameservers ns1.invalid ns2.invalid 141 dns-search bar foo 142 143 iface {ethaa_bb_cc_dd_ee_f0} inet static 144 address 0.1.2.3/24 145 gateway 0.1.2.1 146 mtu 8317 147 148 iface {ethaa_bb_cc_dd_ee_f1} inet static 149 address 0.2.2.4/24 150 post-up ip route add 0.5.6.0/24 via 0.2.2.1 metric 50 151 pre-down ip route del 0.5.6.0/24 via 0.2.2.1 metric 50 152 153 iface {ethaa_bb_cc_dd_ee_f2} inet dhcp 154 155 iface {ethaa_bb_cc_dd_ee_f3} inet dhcp 156 157 iface {ethaa_bb_cc_dd_ee_f4} inet manual 158 159 iface {ethaa_bb_cc_dd_ee_f5} inet6 static 160 address 2001:db8::dead:beef/64 161 gateway 2001:db8::dead:f00 162 ' > '%[1]s.templ' 163 ` 164 s.expectedSampleConfigTemplate = ` 165 auto lo {ethaa_bb_cc_dd_ee_f0} {ethaa_bb_cc_dd_ee_f1} {ethaa_bb_cc_dd_ee_f3} {ethaa_bb_cc_dd_ee_f5} 166 167 iface lo inet loopback 168 dns-nameservers ns1.invalid ns2.invalid 169 dns-search bar foo 170 171 iface {ethaa_bb_cc_dd_ee_f0} inet static 172 address 0.1.2.3/24 173 gateway 0.1.2.1 174 mtu 8317 175 176 iface {ethaa_bb_cc_dd_ee_f1} inet static 177 address 0.2.2.4/24 178 post-up ip route add 0.5.6.0/24 via 0.2.2.1 metric 50 179 pre-down ip route del 0.5.6.0/24 via 0.2.2.1 metric 50 180 181 iface {ethaa_bb_cc_dd_ee_f2} inet dhcp 182 183 iface {ethaa_bb_cc_dd_ee_f3} inet dhcp 184 185 iface {ethaa_bb_cc_dd_ee_f4} inet manual 186 187 iface {ethaa_bb_cc_dd_ee_f5} inet6 static 188 address 2001:db8::dead:beef/64 189 gateway 2001:db8::dead:f00 190 ` 191 192 networkInterfacesScriptYamled := strings.Replace(cloudinit.NetworkInterfacesScript, "\n", "\n ", -1) 193 networkInterfacesScriptYamled = strings.Replace(networkInterfacesScriptYamled, "\n \n", "\n\n", -1) 194 networkInterfacesScriptYamled = strings.Replace(networkInterfacesScriptYamled, "%", "%%", -1) 195 networkInterfacesScriptYamled = strings.Replace(networkInterfacesScriptYamled, "'", "'\"'\"'", -1) 196 197 s.expectedSampleUserData = `- install -D -m 744 /dev/null '%[2]s' 198 - |- 199 printf '%%s\n' '` + networkInterfacesScriptYamled + ` ' > '%[2]s' 200 - |2 201 202 if [ ! -f /sbin/ifup ]; then 203 echo "No /sbin/ifup, applying netplan configuration." 204 netplan generate 205 netplan apply 206 else 207 if [ -f /usr/bin/python ]; then 208 python %[1]s.py --interfaces-file %[1]s --output-file %[1]s.out 209 else 210 python3 %[1]s.py --interfaces-file %[1]s --output-file %[1]s.out 211 fi 212 ifdown -a 213 sleep 1.5 214 mv %[1]s.out %[1]s 215 ifup -a 216 fi 217 `[1:] 218 s.expectedFullNetplanYaml = ` 219 - install -D -m 644 /dev/null '%[1]s' 220 - |- 221 printf '%%s\n' 'network: 222 version: 2 223 ethernets: 224 any0: 225 match: 226 macaddress: aa:bb:cc:dd:ee:f0 227 addresses: 228 - 0.1.2.3/24 229 gateway4: 0.1.2.1 230 nameservers: 231 search: [foo, bar] 232 addresses: [ns1.invalid, ns2.invalid] 233 mtu: 8317 234 any1: 235 match: 236 macaddress: aa:bb:cc:dd:ee:f1 237 addresses: 238 - 0.2.2.4/24 239 gateway4: 0.2.2.1 240 nameservers: 241 search: [foo, bar] 242 addresses: [ns1.invalid, ns2.invalid] 243 routes: 244 - to: 0.5.6.0/24 245 via: 0.2.2.1 246 metric: 50 247 any2: 248 match: 249 macaddress: aa:bb:cc:dd:ee:f2 250 dhcp4: true 251 any3: 252 match: 253 macaddress: aa:bb:cc:dd:ee:f3 254 dhcp4: true 255 any4: 256 match: 257 macaddress: aa:bb:cc:dd:ee:f4 258 any5: 259 match: 260 macaddress: aa:bb:cc:dd:ee:f5 261 addresses: 262 - 2001:db8::dead:beef/64 263 gateway6: 2001:db8::dead:f00 264 ' > '%[1]s' 265 `[1:] 266 267 s.expectedFullNetplan = ` 268 network: 269 version: 2 270 ethernets: 271 any0: 272 match: 273 macaddress: aa:bb:cc:dd:ee:f0 274 addresses: 275 - 0.1.2.3/24 276 gateway4: 0.1.2.1 277 nameservers: 278 search: [foo, bar] 279 addresses: [ns1.invalid, ns2.invalid] 280 mtu: 8317 281 any1: 282 match: 283 macaddress: aa:bb:cc:dd:ee:f1 284 addresses: 285 - 0.2.2.4/24 286 gateway4: 0.2.2.1 287 nameservers: 288 search: [foo, bar] 289 addresses: [ns1.invalid, ns2.invalid] 290 routes: 291 - to: 0.5.6.0/24 292 via: 0.2.2.1 293 metric: 50 294 any2: 295 match: 296 macaddress: aa:bb:cc:dd:ee:f2 297 dhcp4: true 298 any3: 299 match: 300 macaddress: aa:bb:cc:dd:ee:f3 301 dhcp4: true 302 any4: 303 match: 304 macaddress: aa:bb:cc:dd:ee:f4 305 any5: 306 match: 307 macaddress: aa:bb:cc:dd:ee:f5 308 addresses: 309 - 2001:db8::dead:beef/64 310 gateway6: 2001:db8::dead:f00 311 `[1:] 312 313 s.expectedBaseNetplanYaml = ` 314 - install -D -m 644 /dev/null '%[1]s' 315 - |- 316 printf '%%s\n' 'network: 317 version: 2 318 ethernets: 319 eth0: 320 match: 321 name: eth0 322 dhcp4: true 323 ' > '%[1]s' 324 `[1:] 325 326 s.expectedFallbackConfig = `- install -D -m 644 /dev/null '%[1]s.templ' 327 - |- 328 printf '%%s\n' ' 329 auto lo {eth} 330 331 iface lo inet loopback 332 333 iface {eth} inet dhcp 334 ' > '%[1]s.templ' 335 ` 336 337 s.expectedBaseConfig = ` 338 auto lo {eth} 339 340 iface lo inet loopback 341 342 iface {eth} inet dhcp 343 ` 344 345 s.expectedBaseNetplan = ` 346 network: 347 version: 2 348 ethernets: 349 eth0: 350 match: 351 name: eth0 352 dhcp4: true 353 `[1:] 354 355 s.expectedFallbackUserData = ` 356 #cloud-config 357 bootcmd: 358 - install -D -m 644 /dev/null '%[1]s' 359 - |- 360 printf '%%s\n' ' 361 auto eth0 lo 362 363 iface lo inet loopback 364 365 iface eth0 inet dhcp 366 ' > '%[1]s' 367 runcmd: 368 - |- 369 if [ -f %[1]s ]; then 370 echo "stopping all interfaces" 371 ifdown -a 372 sleep 1.5 373 if ifup -a --interfaces=%[1]s; then 374 echo "ifup with %[1]s succeeded, renaming to %[2]s" 375 cp %[2]s %[2]s-orig 376 cp %[1]s %[2]s 377 else 378 echo "ifup with %[1]s failed, leaving old %[2]s alone" 379 ifup -a 380 fi 381 else 382 echo "did not find %[1]s, not reconfiguring networking" 383 fi 384 `[1:] 385 386 s.originalSystemNetworkInterfaces = ` 387 # This file describes the network interfaces available on your system 388 # and how to activate them. For more information, see interfaces(5). 389 390 # The loopback network interface 391 auto lo 392 iface lo inet loopback 393 394 # Source interfaces 395 # Please check /etc/network/interfaces.d before changing this file 396 # as interfaces may have been defined in /etc/network/interfaces.d 397 # See LP: #1262951 398 source /etc/network/interfaces.d/*.cfg 399 `[1:] 400 401 s.PatchValue(cloudinit.NetworkInterfacesFile, s.systemNetworkInterfacesFile) 402 s.PatchValue(cloudinit.SystemNetworkInterfacesFile, s.systemNetworkInterfacesFile) 403 s.PatchValue(cloudinit.JujuNetplanFile, s.jujuNetplanFile) 404 } 405 406 func (s *NetworkUbuntuSuite) TestGenerateENIConfig(c *gc.C) { 407 data, err := cloudinit.GenerateENITemplate(nil) 408 c.Assert(err, gc.ErrorMatches, "missing container network config") 409 c.Assert(data, gc.Equals, "") 410 411 netConfig := container.BridgeNetworkConfig("foo", 0, nil) 412 data, err = cloudinit.GenerateENITemplate(netConfig.Interfaces) 413 c.Assert(err, jc.ErrorIsNil) 414 c.Check(data, gc.Equals, s.expectedBaseConfig) 415 416 // Test with all interface types. 417 netConfig = container.BridgeNetworkConfig("foo", 0, s.fakeInterfaces) 418 data, err = cloudinit.GenerateENITemplate(netConfig.Interfaces) 419 c.Assert(err, jc.ErrorIsNil) 420 c.Check(data, gc.Equals, s.expectedSampleConfigTemplate) 421 } 422 423 func (s *NetworkUbuntuSuite) TestGenerateNetplan(c *gc.C) { 424 data, err := cloudinit.GenerateNetplan(nil) 425 c.Assert(err, gc.ErrorMatches, "missing container network config") 426 c.Assert(data, gc.Equals, "") 427 428 netConfig := container.BridgeNetworkConfig("foo", 0, nil) 429 data, err = cloudinit.GenerateNetplan(netConfig.Interfaces) 430 c.Assert(err, jc.ErrorIsNil) 431 c.Check(data, gc.Equals, s.expectedBaseNetplan) 432 433 // Test with all interface types. 434 netConfig = container.BridgeNetworkConfig("foo", 0, s.fakeInterfaces) 435 data, err = cloudinit.GenerateNetplan(netConfig.Interfaces) 436 c.Assert(err, jc.ErrorIsNil) 437 c.Check(data, gc.Equals, s.expectedFullNetplan) 438 } 439 440 func (s *NetworkUbuntuSuite) TestAddNetworkConfigSampleConfig(c *gc.C) { 441 netConfig := container.BridgeNetworkConfig("foo", 0, s.fakeInterfaces) 442 cloudConf, err := cloudinit.New("xenial") 443 c.Assert(err, jc.ErrorIsNil) 444 c.Assert(cloudConf, gc.NotNil) 445 err = cloudConf.AddNetworkConfig(netConfig.Interfaces) 446 c.Assert(err, jc.ErrorIsNil) 447 448 expected := s.expectedSampleConfigHeader 449 expected += fmt.Sprintf(s.expectedFullNetplanYaml, s.jujuNetplanFile) 450 expected += fmt.Sprintf(s.expectedSampleConfigWriting, s.systemNetworkInterfacesFile) 451 expected += fmt.Sprintf(s.expectedSampleUserData, s.systemNetworkInterfacesFile, s.networkInterfacesPythonFile, s.systemNetworkInterfacesFile) 452 assertUserData(c, cloudConf, expected) 453 } 454 455 func (s *NetworkUbuntuSuite) TestAddNetworkConfigFallbackConfig(c *gc.C) { 456 netConfig := container.BridgeNetworkConfig("foo", 0, nil) 457 cloudConf, err := cloudinit.New("xenial") 458 c.Assert(err, jc.ErrorIsNil) 459 c.Assert(cloudConf, gc.NotNil) 460 err = cloudConf.AddNetworkConfig(netConfig.Interfaces) 461 c.Assert(err, jc.ErrorIsNil) 462 c.Assert(cloudConf, gc.NotNil) 463 expected := s.expectedSampleConfigHeader 464 expected += fmt.Sprintf(s.expectedBaseNetplanYaml, s.jujuNetplanFile) 465 expected += fmt.Sprintf(s.expectedFallbackConfig, s.systemNetworkInterfacesFile, s.systemNetworkInterfacesFile) 466 expected += fmt.Sprintf(s.expectedSampleUserData, s.systemNetworkInterfacesFile, s.networkInterfacesPythonFile) 467 assertUserData(c, cloudConf, expected) 468 } 469 470 func CloudInitDataExcludingOutputSection(data string) []string { 471 // Extract the "#cloud-config" header and all lines between 472 // from the "bootcmd" section up to (but not including) the 473 // "output" sections to match against expected. But we cannot 474 // possibly handle all the /other/ output that may be added by 475 // CloudInitUserData() in the future, so we also truncate at 476 // the first runcmd which now happens to include the runcmd's 477 // added for raising the network interfaces captured in 478 // expectedFallbackUserData. However, the other tests above do 479 // check for that output. 480 481 var linesToMatch []string 482 seenBootcmd := false 483 for _, line := range strings.Split(string(data), "\n") { 484 if strings.HasPrefix(line, "#cloud-config") { 485 linesToMatch = append(linesToMatch, line) 486 continue 487 } 488 489 if strings.HasPrefix(line, "bootcmd:") { 490 seenBootcmd = true 491 } 492 493 if strings.HasPrefix(line, "output:") && seenBootcmd { 494 break 495 } 496 497 if seenBootcmd { 498 linesToMatch = append(linesToMatch, line) 499 } 500 } 501 502 return linesToMatch 503 } 504 505 func assertUserData(c *gc.C, cloudConf cloudinit.CloudConfig, expected string) { 506 data, err := cloudConf.RenderYAML() 507 c.Assert(err, jc.ErrorIsNil) 508 c.Assert(string(data), gc.Equals, expected) 509 510 // Make sure it's valid YAML as well. 511 out := make(map[string]interface{}) 512 err = yaml.Unmarshal(data, &out) 513 c.Assert(err, jc.ErrorIsNil) 514 if len(cloudConf.BootCmds()) > 0 { 515 outcmds := out["bootcmd"].([]interface{}) 516 confcmds := cloudConf.BootCmds() 517 c.Assert(len(outcmds), gc.Equals, len(confcmds)) 518 for i := range outcmds { 519 c.Assert(outcmds[i].(string), gc.Equals, confcmds[i]) 520 } 521 } else { 522 c.Assert(out["bootcmd"], gc.IsNil) 523 } 524 } 525 526 func (s *NetworkUbuntuSuite) TestENIScriptSimple(c *gc.C) { 527 cmd := s.createMockCommand(c, []string{simpleENIFileIPOutput}) 528 s.runENIScriptWithAllPythons(c, cmd, simpleENIFile, simpleENIFileExpected, 0, 1) 529 } 530 531 func (s *NetworkUbuntuSuite) TestENIScriptUnknownMAC(c *gc.C) { 532 cmd := s.createMockCommand(c, []string{unknownMACENIFileIPOutput}) 533 s.runENIScriptWithAllPythons(c, cmd, unknownMACENIFile, unknownMACENIFileExpected, 0, 1) 534 } 535 536 func (s *NetworkUbuntuSuite) TestENIScriptHotplugFail(c *gc.C) { 537 cmd := s.createMockCommand(c, []string{hotplugENIFileIPOutputPre}) 538 s.runENIScriptWithAllPythons(c, cmd, hotplugENIFile, hotplugENIFileExpectedFail, 0, 3) 539 } 540 541 func (s *NetworkUbuntuSuite) TestENIScriptHotplugTooLate(c *gc.C) { 542 for _, python := range s.pythonVersions { 543 c.Logf("test using %s", python) 544 ipCommand := s.createMockCommand(c, []string{hotplugENIFileIPOutputPre, hotplugENIFileIPOutputPre, hotplugENIFileIPOutputPre, hotplugENIFileIPOutputPost}) 545 s.runENIScript(c, python, ipCommand, hotplugENIFile, hotplugENIFileExpectedFail, 0, 3) 546 } 547 } 548 549 func (s *NetworkUbuntuSuite) TestENIScriptHotplug(c *gc.C) { 550 for _, python := range s.pythonVersions { 551 c.Logf("test using %s", python) 552 ipCommand := s.createMockCommand(c, []string{hotplugENIFileIPOutputPre, hotplugENIFileIPOutputPre, hotplugENIFileIPOutputPost}) 553 s.runENIScript(c, python, ipCommand, hotplugENIFile, hotplugENIFileExpected, 0, 3) 554 } 555 } 556 557 func (s *NetworkUbuntuSuite) runENIScriptWithAllPythons(c *gc.C, ipCommand, input, expectedOutput string, wait, retries int) { 558 for _, python := range s.pythonVersions { 559 c.Logf("test using %s", python) 560 s.runENIScript(c, python, ipCommand, input, expectedOutput, wait, retries) 561 } 562 563 } 564 565 func (s *NetworkUbuntuSuite) runENIScript(c *gc.C, pythonBinary, ipCommand, input, expectedOutput string, wait, retries int) { 566 dataFile := filepath.Join(s.tempFolder, "interfaces") 567 dataOutFile := filepath.Join(s.tempFolder, "interfaces.out") 568 dataBakFile := filepath.Join(s.tempFolder, "interfaces.bak") 569 templFile := filepath.Join(s.tempFolder, "interfaces.templ") 570 scriptFile := filepath.Join(s.tempFolder, "script.py") 571 572 err := ioutil.WriteFile(dataFile, []byte(s.originalSystemNetworkInterfaces), 0644) 573 c.Assert(err, jc.ErrorIsNil, gc.Commentf("Can't write interfaces file")) 574 575 err = ioutil.WriteFile(templFile, []byte(input), 0644) 576 c.Assert(err, jc.ErrorIsNil, gc.Commentf("Can't write interfaces.templ file")) 577 578 err = ioutil.WriteFile(scriptFile, []byte(cloudinit.NetworkInterfacesScript), 0755) 579 c.Assert(err, jc.ErrorIsNil, gc.Commentf("Can't write script file")) 580 581 script := fmt.Sprintf("%q %q --interfaces-file %q --output-file %q --command %q --wait %d --retries %d", 582 pythonBinary, scriptFile, dataFile, dataOutFile, ipCommand, wait, retries) 583 result, err := exec.RunCommands(exec.RunParams{Commands: script}) 584 c.Assert(err, jc.ErrorIsNil, gc.Commentf("script failed unexpectedly - %s", result)) 585 c.Logf("%s\n%s\n", string(result.Stdout), string(result.Stderr)) 586 587 for file, expected := range map[string]string{ 588 dataBakFile: s.originalSystemNetworkInterfaces, 589 dataOutFile: expectedOutput, 590 dataFile: s.originalSystemNetworkInterfaces, 591 } { 592 data, err := ioutil.ReadFile(file) 593 c.Assert(err, jc.ErrorIsNil, gc.Commentf("can't open %q file: %s", file, err)) 594 output := string(data) 595 c.Assert(output, gc.Equals, expected) 596 } 597 } 598 599 func (s *NetworkUbuntuSuite) createMockCommand(c *gc.C, outputs []string) string { 600 randBytes := make([]byte, 16) 601 rand.Read(randBytes) 602 baseName := hex.EncodeToString(randBytes) 603 basePath := filepath.Join(s.tempFolder, fmt.Sprintf("%s.%d", baseName, 0)) 604 script := fmt.Sprintf("#!/bin/bash\ncat %s\n", basePath) 605 606 lastFile := "" 607 for i, output := range outputs { 608 dataFile := filepath.Join(s.tempFolder, fmt.Sprintf("%s.%d", baseName, i)) 609 err := ioutil.WriteFile(dataFile, []byte(output), 0644) 610 c.Assert(err, jc.ErrorIsNil, gc.Commentf("can't write mock file")) 611 if lastFile != "" { 612 script += fmt.Sprintf("mv %q %q || true\n", dataFile, lastFile) 613 } 614 lastFile = dataFile 615 } 616 617 scriptPath := filepath.Join(s.tempFolder, fmt.Sprintf("%s.sh", baseName)) 618 err := ioutil.WriteFile(scriptPath, []byte(script), 0755) 619 c.Assert(err, jc.ErrorIsNil, gc.Commentf("can't write script file")) 620 return scriptPath 621 } 622 623 const simpleENIFile = `auto lo 624 interface lo inet loopback 625 626 auto {ethe0_db_55_e4_1d_5b} 627 iface {ethe0_db_55_e4_1d_5b} inet static 628 address 1.2.3.4 629 netmask 255.255.255.0 630 gateway 1.2.3.1 631 632 auto {ethe0_db_55_e4_1a_5b} 633 iface {ethe0_db_55_e4_1a_5b} inet static 634 address 1.2.3.5 635 netmask 255.255.255.0 636 gateway 1.2.3.1 637 638 auto {ethe0_db_55_e4_1a_5d} 639 iface {ethe0_db_55_e4_1a_5d} inet static 640 address 1.2.3.6 641 netmask 255.255.255.0 642 gateway 1.2.3.1 643 ` 644 645 const simpleENIFileIPOutput = `1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 646 2: eno1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1d:5b brd ff:ff:ff:ff:ff:ff 647 3: eno2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1a:5b brd ff:ff:ff:ff:ff:ff 648 3: eno3@if0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1a:5d brd ff:ff:ff:ff:ff:ff 649 ` 650 651 const simpleENIFileExpected = `auto lo 652 interface lo inet loopback 653 654 auto eno1 655 iface eno1 inet static 656 address 1.2.3.4 657 netmask 255.255.255.0 658 gateway 1.2.3.1 659 660 auto eno2 661 iface eno2 inet static 662 address 1.2.3.5 663 netmask 255.255.255.0 664 gateway 1.2.3.1 665 666 auto eno3 667 iface eno3 inet static 668 address 1.2.3.6 669 netmask 255.255.255.0 670 gateway 1.2.3.1 671 ` 672 673 const unknownMACENIFile = `auto lo 674 interface lo inet loopback 675 676 auto {ethe0_db_55_e4_1d_5b} 677 iface {ethe0_db_55_e4_1d_5b} inet static 678 address 1.2.3.4 679 netmask 255.255.255.0 680 gateway 1.2.3.1 681 682 auto {ethe3_db_55_e4_1d_5b} 683 iface {ethe3_db_55_e4_1d_5b} inet static 684 address 1.2.3.5 685 netmask 255.255.255.0 686 gateway 1.2.3.1 687 ` 688 689 const unknownMACENIFileIPOutput = `1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 690 2: eno1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1d:5b brd ff:ff:ff:ff:ff:ff 691 ` 692 693 const unknownMACENIFileExpected = `auto lo 694 interface lo inet loopback 695 696 auto eno1 697 iface eno1 inet static 698 address 1.2.3.4 699 netmask 255.255.255.0 700 gateway 1.2.3.1 701 702 auto ethe3_db_55_e4_1d_5b 703 iface ethe3_db_55_e4_1d_5b inet static 704 address 1.2.3.5 705 netmask 255.255.255.0 706 gateway 1.2.3.1 707 ` 708 709 const hotplugENIFile = `auto lo 710 interface lo inet loopback 711 712 auto {ethe0_db_55_e4_1d_5b} 713 iface {ethe0_db_55_e4_1d_5b} inet static 714 address 1.2.3.4 715 netmask 255.255.255.0 716 gateway 1.2.3.1 717 718 auto {ethe0_db_55_e4_1a_5b} 719 iface {ethe0_db_55_e4_1a_5b} inet static 720 address 1.2.3.5 721 netmask 255.255.255.0 722 gateway 1.2.3.1 723 ` 724 725 const hotplugENIFileIPOutputPre = `1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 726 2: eno1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1d:5b brd ff:ff:ff:ff:ff:ff 727 ` 728 729 const hotplugENIFileIPOutputPost = `1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1\ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 730 2: eno1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1d:5b brd ff:ff:ff:ff:ff:ff 731 32: eno2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000\ link/ether e0:db:55:e4:1a:5b brd ff:ff:ff:ff:ff:ff 732 ` 733 734 const hotplugENIFileExpected = `auto lo 735 interface lo inet loopback 736 737 auto eno1 738 iface eno1 inet static 739 address 1.2.3.4 740 netmask 255.255.255.0 741 gateway 1.2.3.1 742 743 auto eno2 744 iface eno2 inet static 745 address 1.2.3.5 746 netmask 255.255.255.0 747 gateway 1.2.3.1 748 ` 749 750 const hotplugENIFileExpectedFail = `auto lo 751 interface lo inet loopback 752 753 auto eno1 754 iface eno1 inet static 755 address 1.2.3.4 756 netmask 255.255.255.0 757 gateway 1.2.3.1 758 759 auto ethe0_db_55_e4_1a_5b 760 iface ethe0_db_55_e4_1a_5b inet static 761 address 1.2.3.5 762 netmask 255.255.255.0 763 gateway 1.2.3.1 764 `