github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/network/netplan/netplan_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package netplan_test 5 6 import ( 7 "fmt" 8 "io" 9 "math/rand" 10 "os" 11 "path" 12 "reflect" 13 "strings" 14 15 "github.com/juju/errors" 16 "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 "github.com/kr/pretty" 19 gc "gopkg.in/check.v1" 20 "gopkg.in/yaml.v2" 21 goyaml "gopkg.in/yaml.v2" 22 23 "github.com/juju/juju/network/netplan" 24 coretesting "github.com/juju/juju/testing" 25 ) 26 27 type NetplanSuite struct { 28 testing.IsolationSuite 29 } 30 31 var _ = gc.Suite(&NetplanSuite{}) 32 33 func MustNetplanFromYaml(c *gc.C, input string) *netplan.Netplan { 34 var np netplan.Netplan 35 if strings.HasPrefix(input, "\n") { 36 input = input[1:] 37 } 38 err := goyaml.UnmarshalStrict([]byte(input), &np) 39 c.Assert(err, jc.ErrorIsNil) 40 return &np 41 } 42 43 func checkNetplanRoundTrips(c *gc.C, input string) { 44 if strings.HasPrefix(input, "\n") { 45 input = input[1:] 46 } 47 np := MustNetplanFromYaml(c, input) 48 out, err := netplan.Marshal(np) 49 c.Assert(err, jc.ErrorIsNil) 50 c.Check(string(out), gc.Equals, input) 51 } 52 53 func (s *NetplanSuite) TestStructures(c *gc.C) { 54 checkNetplanRoundTrips(c, ` 55 network: 56 version: 2 57 renderer: NetworkManager 58 ethernets: 59 id0: 60 match: 61 macaddress: "00:11:22:33:44:55" 62 wakeonlan: true 63 addresses: 64 - 192.168.14.2/24 65 - 2001:1::1/64 66 critical: true 67 dhcp4: true 68 dhcp-identifier: mac 69 gateway4: 192.168.14.1 70 gateway6: 2001:1::2 71 nameservers: 72 search: [foo.local, bar.local] 73 addresses: [8.8.8.8] 74 routes: 75 - to: 0.0.0.0/0 76 via: 11.0.0.1 77 metric: 3 78 lom: 79 match: 80 driver: ixgbe 81 set-name: lom1 82 dhcp6: true 83 switchports: 84 match: 85 name: enp2* 86 mtu: 1280 87 wifis: 88 all-wlans: 89 access-points: 90 Joe's home: 91 password: s3kr1t 92 wlp1s0: 93 access-points: 94 guest: 95 mode: ap 96 channel: 11 97 bridges: 98 br0: 99 interfaces: [wlp1s0, switchports] 100 dhcp4: false 101 ovs0: 102 interfaces: [patch0-1, eth0, bond0] 103 addresses: 104 - 10.5.48.11/20 105 openvswitch: 106 controller: 107 addresses: 108 - unix:/var/run/openvswitch/ovs0.mgmt 109 connection-mode: out-of-band 110 external-ids: 111 iface-id: myhostname 112 fail-mode: secure 113 mcast-snooping: true 114 other-config: 115 disable-in-band: true 116 protocols: 117 - OpenFlow10 118 - OpenFlow11 119 - OpenFlow12 120 routes: 121 - to: 0.0.0.0/0 122 via: 11.0.0.1 123 metric: 3 124 `) 125 } 126 127 func (s *NetplanSuite) TestStructuresWithOptionalOVSBlock(c *gc.C) { 128 checkNetplanRoundTrips(c, ` 129 network: 130 version: 2 131 renderer: NetworkManager 132 bridges: 133 br0: 134 interfaces: [wlp1s0, switchports] 135 dhcp4: false 136 ovs0: 137 interfaces: [patch0-1, eth0, bond0] 138 addresses: 139 - 10.5.48.11/20 140 openvswitch: {} 141 `) 142 } 143 144 func (s *NetplanSuite) TestSerializationOfEthernetDevicesWithLinkLocalFields(c *gc.C) { 145 np := MustNetplanFromYaml(c, ` 146 network: 147 version: 2 148 ethernets: 149 eth0: 150 link-local: [ipv4, ipv6] 151 eth1: 152 link-local: [] 153 eth2: 154 critical: true 155 `) 156 157 exp := ` 158 network: 159 version: 2 160 ethernets: 161 eth0: 162 link-local: 163 - ipv4 164 - ipv6 165 eth1: 166 link-local: [] 167 eth2: 168 critical: true 169 `[1:] 170 171 npYAML, err := netplan.Marshal(np) 172 c.Assert(err, jc.ErrorIsNil) 173 c.Assert(string(npYAML), gc.Equals, exp) 174 } 175 176 func (s *NetplanSuite) TestBasicBond(c *gc.C) { 177 checkNetplanRoundTrips(c, ` 178 network: 179 version: 2 180 renderer: NetworkManager 181 ethernets: 182 id0: 183 match: 184 macaddress: "00:11:22:33:44:55" 185 set-name: id0 186 id1: 187 match: 188 macaddress: de:ad:be:ef:01:02 189 set-name: id1 190 bridges: 191 br-bond0: 192 interfaces: [bond0] 193 dhcp4: true 194 bonds: 195 bond0: 196 interfaces: [id0, id1] 197 parameters: 198 mode: 802.3ad 199 lacp-rate: fast 200 mii-monitor-interval: 100 201 transmit-hash-policy: layer2 202 up-delay: 0 203 down-delay: 0 204 `) 205 } 206 207 func (s *NetplanSuite) TestParseBridgedBond(c *gc.C) { 208 checkNetplanRoundTrips(c, ` 209 network: 210 version: 2 211 renderer: NetworkManager 212 ethernets: 213 id0: 214 match: 215 macaddress: "00:11:22:33:44:55" 216 set-name: id0 217 id1: 218 match: 219 macaddress: de:ad:be:ef:01:02 220 set-name: id1 221 bridges: 222 br-bond0: 223 interfaces: [bond0] 224 dhcp4: true 225 bonds: 226 bond0: 227 interfaces: [id0, id1] 228 parameters: 229 mode: 802.3ad 230 lacp-rate: fast 231 mii-monitor-interval: 100 232 transmit-hash-policy: layer2 233 up-delay: 0 234 down-delay: 0 235 `) 236 } 237 238 func (s *NetplanSuite) TestBondsIntParameters(c *gc.C) { 239 // several parameters can be specified as an integer or a string 240 // such as 'mode: 0' is the same as 'balance-rr' 241 checkNetplanRoundTrips(c, ` 242 network: 243 version: 2 244 renderer: NetworkManager 245 ethernets: 246 id0: 247 match: 248 macaddress: "00:11:22:33:44:55" 249 set-name: id0 250 id1: 251 match: 252 macaddress: de:ad:be:ef:01:02 253 set-name: id1 254 bonds: 255 bond0: 256 interfaces: [id0, id1] 257 parameters: 258 mode: 0 259 lacp-rate: 1 260 ad-select: 1 261 all-slaves-active: true 262 arp-validate: 0 263 arp-all-targets: 0 264 fail-over-mac-policy: 1 265 primary-reselect-policy: 1 266 `) 267 checkNetplanRoundTrips(c, ` 268 network: 269 version: 2 270 renderer: NetworkManager 271 ethernets: 272 id0: 273 match: 274 macaddress: "00:11:22:33:44:55" 275 set-name: id0 276 id1: 277 match: 278 macaddress: de:ad:be:ef:01:02 279 set-name: id1 280 bonds: 281 bond0: 282 interfaces: [id0, id1] 283 parameters: 284 mode: balance-rr 285 lacp-rate: fast 286 ad-select: bandwidth 287 all-slaves-active: false 288 arp-validate: filter 289 arp-all-targets: all 290 fail-over-mac-policy: follow 291 primary-reselect-policy: always 292 `) 293 } 294 295 func (s *NetplanSuite) TestBondWithVLAN(c *gc.C) { 296 checkNetplanRoundTrips(c, ` 297 network: 298 version: 2 299 renderer: NetworkManager 300 ethernets: 301 id0: 302 match: 303 macaddress: "00:11:22:33:44:55" 304 set-name: id0 305 id1: 306 match: 307 macaddress: de:ad:be:ef:01:02 308 set-name: id1 309 bonds: 310 bond0: 311 interfaces: [id0, id1] 312 parameters: 313 mode: 802.3ad 314 lacp-rate: fast 315 mii-monitor-interval: 100 316 transmit-hash-policy: layer2 317 up-delay: 0 318 down-delay: 0 319 vlans: 320 bond0.209: 321 id: 209 322 link: bond0 323 addresses: 324 - 123.123.123.123/24 325 nameservers: 326 addresses: [8.8.8.8] 327 `) 328 } 329 330 func (s *NetplanSuite) TestBondsAllParameters(c *gc.C) { 331 // All parameters don't inherently make sense at the same time, but we should be able to parse all of them. 332 // nolint: misspell 333 checkNetplanRoundTrips(c, ` 334 network: 335 version: 2 336 renderer: NetworkManager 337 ethernets: 338 id0: 339 match: 340 macaddress: "00:11:22:33:44:55" 341 set-name: id0 342 id1: 343 match: 344 macaddress: de:ad:be:ef:01:02 345 set-name: id1 346 id2: 347 match: 348 macaddress: de:ad:be:ef:01:03 349 id3: 350 match: 351 macaddress: de:ad:be:ef:01:04 352 bonds: 353 bond0: 354 interfaces: [id0, id1] 355 parameters: 356 mode: 802.3ad 357 lacp-rate: fast 358 mii-monitor-interval: 100 359 min-links: 0 360 transmit-hash-policy: layer2 361 ad-select: 1 362 all-slaves-active: true 363 arp-interval: 100 364 arp-ip-targets: 365 - 192.168.0.1 366 - 192.168.10.20 367 arp-validate: none 368 arp-all-targets: all 369 up-delay: 0 370 down-delay: 0 371 fail-over-mac-policy: follow 372 gratuitious-arp: 0 373 packets-per-slave: 0 374 primary-reselect-policy: better 375 resend-igmp: 0 376 learn-packet-interval: 4660 377 primary: id1 378 `) 379 } 380 381 func (s *NetplanSuite) TestBridgesAllParameters(c *gc.C) { 382 // All parameters don't inherently make sense at the same time, but we should be able to parse all of them. 383 checkNetplanRoundTrips(c, ` 384 network: 385 version: 2 386 renderer: NetworkManager 387 ethernets: 388 id0: 389 match: 390 macaddress: "00:11:22:33:44:55" 391 set-name: id0 392 id1: 393 match: 394 macaddress: de:ad:be:ef:01:02 395 set-name: id1 396 id2: 397 match: 398 macaddress: de:ad:be:ef:01:03 399 set-name: id2 400 bridges: 401 br-id0: 402 interfaces: [id0] 403 accept-ra: true 404 addresses: 405 - 123.123.123.123/24 406 dhcp4: false 407 dhcp6: true 408 dhcp-identifier: duid 409 parameters: 410 ageing-time: 0 411 forward-delay: 0 412 hello-time: 0 413 max-age: 0 414 path-cost: 415 id0: 0 416 port-priority: 417 id0: 0 418 priority: 0 419 stp: false 420 br-id1: 421 interfaces: [id1] 422 accept-ra: false 423 addresses: 424 - 2001::1/64 425 dhcp4: true 426 dhcp6: true 427 dhcp-identifier: mac 428 parameters: 429 ageing-time: 100 430 forward-delay: 10 431 hello-time: 20 432 max-age: 10 433 path-cost: 434 id1: 50 435 port-priority: 436 id1: 50 437 priority: 20000 438 stp: true 439 br-id2: 440 interfaces: [id2] 441 br-id3: 442 interfaces: [id2] 443 parameters: 444 ageing-time: 10 445 `) 446 } 447 448 func (s *NetplanSuite) TestAllRoutesParams(c *gc.C) { 449 checkNetplanRoundTrips(c, ` 450 network: 451 version: 2 452 renderer: NetworkManager 453 ethernets: 454 id0: 455 match: 456 macaddress: "00:11:22:33:44:55" 457 set-name: id0 458 routes: 459 - from: 192.168.0.0/24 460 on-link: true 461 scope: global 462 table: 1234 463 to: 192.168.3.1/24 464 type: unicast 465 via: 192.168.3.1 466 metric: 1234567 467 - on-link: false 468 to: 192.168.5.1/24 469 via: 192.168.5.1 470 metric: 0 471 - to: 192.168.5.1/24 472 type: unreachable 473 via: 192.168.5.1 474 routing-policy: 475 - from: 192.168.10.0/24 476 mark: 123 477 priority: 10 478 table: 1234 479 to: 192.168.3.1/24 480 type-of-service: 0 481 - from: 192.168.12.0/24 482 mark: 0 483 priority: 0 484 table: 0 485 to: 192.168.3.1/24 486 type-of-service: 255 487 `) 488 } 489 490 func (s *NetplanSuite) TestAllVLANParams(c *gc.C) { 491 checkNetplanRoundTrips(c, ` 492 network: 493 version: 2 494 renderer: NetworkManager 495 ethernets: 496 id0: 497 match: 498 macaddress: "00:11:22:33:44:55" 499 set-name: id0 500 vlans: 501 id0.123: 502 id: 123 503 link: id0 504 accept-ra: true 505 addresses: 506 - 123.123.123.123/24 507 critical: true 508 dhcp4: false 509 dhcp6: false 510 dhcp-identifier: duid 511 gateway4: 123.123.123.123 512 gateway6: dead::beef 513 nameservers: 514 addresses: [8.8.8.8] 515 macaddress: de:ad:be:ef:12:34 516 mtu: 9000 517 renderer: NetworkManager 518 routes: 519 - table: 102 520 to: 100.0.0.0/8 521 via: 1.2.3.10 522 metric: 5 523 routing-policy: 524 - from: 192.168.5.0/24 525 table: 103 526 optional: true 527 id0.456: 528 id: 456 529 link: id0 530 accept-ra: false 531 `) 532 } 533 534 func (s *NetplanSuite) TestSimpleBridger(c *gc.C) { 535 np := MustNetplanFromYaml(c, ` 536 network: 537 version: 2 538 renderer: NetworkManager 539 ethernets: 540 id0: 541 match: 542 macaddress: "00:11:22:33:44:55" 543 addresses: 544 - 1.2.3.4/24 545 - 2000::1/64 546 gateway4: 1.2.3.5 547 gateway6: 2000::2 548 nameservers: 549 search: [foo.local, bar.local] 550 addresses: [8.8.8.8] 551 routes: 552 - to: 100.0.0.0/8 553 via: 1.2.3.10 554 metric: 5 555 `) 556 expected := ` 557 network: 558 version: 2 559 renderer: NetworkManager 560 ethernets: 561 id0: 562 match: 563 macaddress: "00:11:22:33:44:55" 564 bridges: 565 juju-bridge: 566 interfaces: [id0] 567 addresses: 568 - 1.2.3.4/24 569 - 2000::1/64 570 gateway4: 1.2.3.5 571 gateway6: 2000::2 572 nameservers: 573 search: [foo.local, bar.local] 574 addresses: [8.8.8.8] 575 routes: 576 - to: 100.0.0.0/8 577 via: 1.2.3.10 578 metric: 5 579 `[1:] 580 err := np.BridgeEthernetById("id0", "juju-bridge") 581 c.Assert(err, jc.ErrorIsNil) 582 583 out, err := netplan.Marshal(np) 584 c.Assert(err, jc.ErrorIsNil) 585 c.Check(string(out), gc.Equals, expected) 586 } 587 588 func (s *NetplanSuite) TestBridgerIdempotent(c *gc.C) { 589 input := ` 590 network: 591 version: 2 592 renderer: NetworkManager 593 ethernets: 594 id0: 595 match: 596 macaddress: "00:11:22:33:44:55" 597 bridges: 598 juju-bridge: 599 interfaces: [id0] 600 addresses: 601 - 1.2.3.4/24 602 - 2000::1/64 603 gateway4: 1.2.3.5 604 gateway6: 2000::2 605 nameservers: 606 search: [foo.local, bar.local] 607 addresses: [8.8.8.8] 608 routes: 609 - to: 100.0.0.0/8 610 via: 1.2.3.10 611 metric: 5 612 `[1:] 613 np := MustNetplanFromYaml(c, input) 614 c.Assert(np.BridgeEthernetById("id0", "juju-bridge"), jc.ErrorIsNil) 615 out, err := netplan.Marshal(np) 616 c.Check(string(out), gc.Equals, input) 617 c.Assert(err, jc.ErrorIsNil) 618 } 619 620 func (s *NetplanSuite) TestBridgerBridgeExists(c *gc.C) { 621 np := MustNetplanFromYaml(c, ` 622 network: 623 version: 2 624 renderer: NetworkManager 625 ethernets: 626 id0: 627 match: 628 macaddress: "00:11:22:33:44:55" 629 addresses: 630 - 1.2.3.4/24 631 - 2000::1/64 632 gateway4: 1.2.3.5 633 gateway6: 2000::2 634 nameservers: 635 search: [foo.local, bar.local] 636 addresses: [8.8.8.8] 637 id1: 638 match: 639 driver: ixgbe 640 bridges: 641 juju-bridge: 642 interfaces: [id1] 643 addresses: 644 - 1.2.3.4/24 645 - 2000::1/64 646 gateway4: 1.2.3.5 647 gateway6: 2000::2 648 nameservers: 649 search: [foo.local, bar.local] 650 addresses: [8.8.8.8] 651 `) 652 err := np.BridgeEthernetById("id0", "juju-bridge") 653 c.Check(err, gc.ErrorMatches, `cannot create bridge "juju-bridge" with device "id0" - bridge "juju-bridge" w/ interfaces "id1" already exists`) 654 } 655 656 func (s *NetplanSuite) TestBridgerDeviceBridged(c *gc.C) { 657 np := MustNetplanFromYaml(c, ` 658 network: 659 version: 2 660 renderer: NetworkManager 661 ethernets: 662 id0: 663 match: 664 macaddress: "00:11:22:33:44:55" 665 addresses: 666 - 1.2.3.4/24 667 - 2000::1/64 668 gateway4: 1.2.3.5 669 gateway6: 2000::2 670 nameservers: 671 search: [foo.local, bar.local] 672 addresses: [8.8.8.8] 673 bridges: 674 not-juju-bridge: 675 interfaces: [id0] 676 addresses: 677 - 1.2.3.4/24 678 - 2000::1/64 679 gateway4: 1.2.3.5 680 gateway6: 2000::2 681 nameservers: 682 search: [foo.local, bar.local] 683 addresses: [8.8.8.8] 684 `) 685 err := np.BridgeEthernetById("id0", "juju-bridge") 686 c.Check(err, gc.ErrorMatches, `cannot create bridge "juju-bridge", device "id0" in bridge "not-juju-bridge" already exists`) 687 } 688 689 func (s *NetplanSuite) TestBridgerEthernetMissing(c *gc.C) { 690 np := MustNetplanFromYaml(c, ` 691 network: 692 version: 2 693 renderer: NetworkManager 694 ethernets: 695 id0: 696 match: 697 macaddress: "00:11:22:33:44:55" 698 bridges: 699 not-juju-bridge: 700 interfaces: [id0] 701 addresses: 702 - 1.2.3.4/24 703 - 2000::1/64 704 gateway4: 1.2.3.5 705 gateway6: 2000::2 706 nameservers: 707 search: [foo.local, bar.local] 708 addresses: [8.8.8.8] 709 `) 710 err := np.BridgeEthernetById("id7", "juju-bridge") 711 c.Check(err, gc.ErrorMatches, `ethernet device with id "id7" for bridge "juju-bridge" not found`) 712 c.Check(err, jc.Satisfies, errors.IsNotFound) 713 } 714 715 func (s *NetplanSuite) TestBridgeVLAN(c *gc.C) { 716 np := MustNetplanFromYaml(c, ` 717 network: 718 version: 2 719 renderer: NetworkManager 720 ethernets: 721 id0: 722 match: 723 macaddress: "00:11:22:33:44:55" 724 vlans: 725 id0.1234: 726 link: id0 727 id: 1234 728 addresses: 729 - 1.2.3.4/24 730 - 2000::1/64 731 gateway4: 1.2.3.5 732 gateway6: 2000::2 733 macaddress: "00:11:22:33:44:55" 734 nameservers: 735 search: [foo.local, bar.local] 736 addresses: [8.8.8.8] 737 routes: 738 - to: 100.0.0.0/8 739 via: 1.2.3.10 740 metric: 5 741 `) 742 expected := ` 743 network: 744 version: 2 745 renderer: NetworkManager 746 ethernets: 747 id0: 748 match: 749 macaddress: "00:11:22:33:44:55" 750 bridges: 751 br-id0.1234: 752 interfaces: [id0.1234] 753 addresses: 754 - 1.2.3.4/24 755 - 2000::1/64 756 gateway4: 1.2.3.5 757 gateway6: 2000::2 758 nameservers: 759 search: [foo.local, bar.local] 760 addresses: [8.8.8.8] 761 macaddress: "00:11:22:33:44:55" 762 routes: 763 - to: 100.0.0.0/8 764 via: 1.2.3.10 765 metric: 5 766 vlans: 767 id0.1234: 768 id: 1234 769 link: id0 770 `[1:] 771 err := np.BridgeVLANById("id0.1234", "br-id0.1234") 772 c.Assert(err, jc.ErrorIsNil) 773 774 out, err := netplan.Marshal(np) 775 c.Assert(err, jc.ErrorIsNil) 776 c.Check(string(out), gc.Equals, expected) 777 } 778 779 func (s *NetplanSuite) TestBridgerVLANMissing(c *gc.C) { 780 np := MustNetplanFromYaml(c, ` 781 network: 782 version: 2 783 renderer: NetworkManager 784 ethernets: 785 id0: 786 match: 787 macaddress: "00:11:22:33:44:55" 788 vlans: 789 id0.1234: 790 link: id0 791 id: 1234 792 bridges: 793 not-juju-bridge: 794 interfaces: [id0] 795 addresses: 796 - 1.2.3.4/24 797 - 2000::1/64 798 gateway4: 1.2.3.5 799 gateway6: 2000::2 800 nameservers: 801 search: [foo.local, bar.local] 802 addresses: [8.8.8.8] 803 `) 804 err := np.BridgeVLANById("id0.1235", "br-id0.1235") 805 c.Check(err, gc.ErrorMatches, `VLAN device with id "id0.1235" for bridge "br-id0.1235" not found`) 806 c.Check(err, jc.Satisfies, errors.IsNotFound) 807 } 808 809 func (s *NetplanSuite) TestBridgeVLANAndLinkedDevice(c *gc.C) { 810 np := MustNetplanFromYaml(c, ` 811 network: 812 version: 2 813 renderer: NetworkManager 814 ethernets: 815 id0: 816 match: 817 macaddress: "00:11:22:33:44:55" 818 addresses: 819 - 2.3.4.5/24 820 macaddress: "00:11:22:33:44:55" 821 vlans: 822 id0.1234: 823 link: id0 824 id: 1234 825 addresses: 826 - 1.2.3.4/24 827 - 2000::1/64 828 gateway4: 1.2.3.5 829 gateway6: 2000::2 830 macaddress: "00:11:22:33:44:55" 831 nameservers: 832 search: [foo.local, bar.local] 833 addresses: [8.8.8.8] 834 routes: 835 - to: 100.0.0.0/8 836 via: 1.2.3.10 837 metric: 5 838 `) 839 expected := ` 840 network: 841 version: 2 842 renderer: NetworkManager 843 ethernets: 844 id0: 845 match: 846 macaddress: "00:11:22:33:44:55" 847 bridges: 848 br-id0: 849 interfaces: [id0] 850 addresses: 851 - 2.3.4.5/24 852 macaddress: "00:11:22:33:44:55" 853 br-id0.1234: 854 interfaces: [id0.1234] 855 addresses: 856 - 1.2.3.4/24 857 - 2000::1/64 858 gateway4: 1.2.3.5 859 gateway6: 2000::2 860 nameservers: 861 search: [foo.local, bar.local] 862 addresses: [8.8.8.8] 863 macaddress: "00:11:22:33:44:55" 864 routes: 865 - to: 100.0.0.0/8 866 via: 1.2.3.10 867 metric: 5 868 vlans: 869 id0.1234: 870 id: 1234 871 link: id0 872 `[1:] 873 err := np.BridgeEthernetById("id0", "br-id0") 874 c.Assert(err, jc.ErrorIsNil) 875 err = np.BridgeVLANById("id0.1234", "br-id0.1234") 876 c.Assert(err, jc.ErrorIsNil) 877 878 out, err := netplan.Marshal(np) 879 c.Assert(err, jc.ErrorIsNil) 880 c.Check(string(out), gc.Equals, expected) 881 } 882 883 func (s *NetplanSuite) TestBridgeBond(c *gc.C) { 884 np := MustNetplanFromYaml(c, ` 885 network: 886 version: 2 887 renderer: NetworkManager 888 ethernets: 889 id0: 890 match: 891 macaddress: de:ad:22:33:44:55 892 id1: 893 match: 894 macaddress: de:ad:22:33:44:66 895 bonds: 896 bond0: 897 interfaces: [id0, id1] 898 addresses: 899 - 1.2.3.4/24 900 - 2000::1/64 901 gateway4: 1.2.3.5 902 gateway6: 2000::2 903 nameservers: 904 search: [foo.local, bar.local] 905 addresses: [8.8.8.8] 906 routes: 907 - to: 100.0.0.0/8 908 via: 1.2.3.10 909 metric: 5 910 parameters: 911 lacp-rate: fast 912 `) 913 expected := ` 914 network: 915 version: 2 916 renderer: NetworkManager 917 ethernets: 918 id0: 919 match: 920 macaddress: de:ad:22:33:44:55 921 id1: 922 match: 923 macaddress: de:ad:22:33:44:66 924 bridges: 925 br-bond0: 926 interfaces: [bond0] 927 addresses: 928 - 1.2.3.4/24 929 - 2000::1/64 930 gateway4: 1.2.3.5 931 gateway6: 2000::2 932 nameservers: 933 search: [foo.local, bar.local] 934 addresses: [8.8.8.8] 935 routes: 936 - to: 100.0.0.0/8 937 via: 1.2.3.10 938 metric: 5 939 bonds: 940 bond0: 941 interfaces: [id0, id1] 942 parameters: 943 lacp-rate: fast 944 `[1:] 945 err := np.BridgeBondById("bond0", "br-bond0") 946 c.Assert(err, jc.ErrorIsNil) 947 948 out, err := netplan.Marshal(np) 949 c.Assert(err, jc.ErrorIsNil) 950 c.Check(string(out), gc.Equals, expected) 951 } 952 953 func (s *NetplanSuite) TestBridgerBondMissing(c *gc.C) { 954 np := MustNetplanFromYaml(c, ` 955 network: 956 version: 2 957 renderer: NetworkManager 958 ethernets: 959 id0: 960 match: 961 macaddress: "00:11:22:33:44:55" 962 id1: 963 match: 964 macaddress: "00:11:22:33:44:66" 965 vlans: 966 id0.1234: 967 link: id0 968 id: 1234 969 bonds: 970 bond0: 971 interfaces: [id0, id1] 972 bridges: 973 not-juju-bridge: 974 interfaces: [bond0] 975 addresses: 976 - 1.2.3.4/24 977 - 2000::1/64 978 gateway4: 1.2.3.5 979 gateway6: 2000::2 980 nameservers: 981 search: [foo.local, bar.local] 982 addresses: [8.8.8.8] 983 `) 984 err := np.BridgeBondById("bond1", "br-bond1") 985 c.Check(err, gc.ErrorMatches, `bond device with id "bond1" for bridge "br-bond1" not found`) 986 c.Check(err, jc.Satisfies, errors.IsNotFound) 987 } 988 989 func (s *NetplanSuite) TestFindEthernetByName(c *gc.C) { 990 np := MustNetplanFromYaml(c, ` 991 network: 992 version: 2 993 renderer: NetworkManager 994 ethernets: 995 id0: 996 match: 997 macaddress: "00:11:22:33:44:55" 998 addresses: 999 - 1.2.3.4/24 1000 - 2000::1/64 1001 gateway4: 1.2.3.5 1002 gateway6: 2000::2 1003 set-name: eno1 1004 nameservers: 1005 search: [foo.local, bar.local] 1006 addresses: [8.8.8.8] 1007 id1: 1008 match: 1009 macaddress: "00:11:22:33:44:66" 1010 name: en*3 1011 addresses: 1012 - 1.2.4.4/24 1013 - 2001::1/64 1014 gateway4: 1.2.4.5 1015 gateway6: 2001::2 1016 nameservers: 1017 search: [baz.local] 1018 addresses: [8.8.4.4] 1019 eno7: 1020 addresses: 1021 - 3.4.5.6/24 1022 `) 1023 device, err := np.FindEthernetByName("eno1") 1024 c.Assert(err, jc.ErrorIsNil) 1025 c.Check(device, gc.Equals, "id0") 1026 1027 device, err = np.FindEthernetByName("eno3") 1028 c.Assert(err, jc.ErrorIsNil) 1029 c.Check(device, gc.Equals, "id1") 1030 1031 device, err = np.FindEthernetByName("eno7") 1032 c.Assert(err, jc.ErrorIsNil) 1033 c.Check(device, gc.Equals, "eno7") 1034 1035 _, err = np.FindEthernetByName("eno5") 1036 c.Check(err, gc.ErrorMatches, "Ethernet device with name \"eno5\" not found") 1037 c.Check(err, jc.Satisfies, errors.IsNotFound) 1038 } 1039 1040 func (s *NetplanSuite) TestFindEthernetByMAC(c *gc.C) { 1041 np := MustNetplanFromYaml(c, ` 1042 network: 1043 version: 2 1044 renderer: NetworkManager 1045 ethernets: 1046 id0: 1047 match: 1048 macaddress: "00:11:22:33:44:55" 1049 addresses: 1050 - 1.2.3.4/24 1051 - 2000::1/64 1052 gateway4: 1.2.3.5 1053 gateway6: 2000::2 1054 set-name: eno1 1055 nameservers: 1056 search: [foo.local, bar.local] 1057 addresses: [8.8.8.8] 1058 id1: 1059 match: 1060 macaddress: "00:11:22:33:44:66" 1061 addresses: 1062 - 1.2.4.4/24 1063 - 2001::1/64 1064 gateway4: 1.2.4.5 1065 gateway6: 2001::2 1066 nameservers: 1067 search: [baz.local] 1068 addresses: [8.8.4.4] 1069 id2: 1070 addresses: 1071 - 2.3.4.5/24 1072 macaddress: 00:11:22:33:44:77 1073 `) 1074 device, err := np.FindEthernetByMAC("00:11:22:33:44:66") 1075 c.Assert(err, jc.ErrorIsNil) 1076 c.Check(device, gc.Equals, "id1") 1077 1078 _, err = np.FindEthernetByMAC("00:11:22:33:44:88") 1079 c.Check(err, gc.ErrorMatches, "Ethernet device with MAC \"00:11:22:33:44:88\" not found") 1080 c.Check(err, jc.Satisfies, errors.IsNotFound) 1081 1082 device, err = np.FindEthernetByMAC("00:11:22:33:44:77") 1083 c.Assert(err, jc.ErrorIsNil) 1084 c.Check(device, gc.Equals, "id2") 1085 } 1086 1087 func (s *NetplanSuite) TestFindVLANByName(c *gc.C) { 1088 input := ` 1089 network: 1090 version: 2 1091 renderer: NetworkManager 1092 ethernets: 1093 id0: 1094 match: 1095 macaddress: "00:11:22:33:44:55" 1096 addresses: 1097 - 1.2.3.4/24 1098 - 2000::1/64 1099 gateway4: 1.2.3.5 1100 gateway6: 2000::2 1101 set-name: eno1 1102 nameservers: 1103 search: [foo.local, bar.local] 1104 addresses: [8.8.8.8] 1105 vlans: 1106 id0.123: 1107 link: id0 1108 addresses: 1109 - 2.3.4.5/24 1110 `[1:] 1111 np := MustNetplanFromYaml(c, input) 1112 1113 device, err := np.FindVLANByName("id0.123") 1114 c.Assert(err, jc.ErrorIsNil) 1115 c.Check(device, gc.Equals, "id0.123") 1116 1117 _, err = np.FindVLANByName("id0") 1118 c.Check(err, gc.ErrorMatches, "VLAN device with name \"id0\" not found") 1119 c.Check(err, jc.Satisfies, errors.IsNotFound) 1120 } 1121 1122 func (s *NetplanSuite) TestFindVLANByMAC(c *gc.C) { 1123 input := ` 1124 network: 1125 version: 2 1126 renderer: NetworkManager 1127 ethernets: 1128 id0: 1129 match: 1130 macaddress: "00:11:22:33:44:55" 1131 addresses: 1132 - 1.2.3.4/24 1133 - 2000::1/64 1134 gateway4: 1.2.3.5 1135 gateway6: 2000::2 1136 set-name: eno1 1137 nameservers: 1138 search: [foo.local, bar.local] 1139 addresses: [8.8.8.8] 1140 vlans: 1141 id0.123: 1142 id: 123 1143 link: id0 1144 addresses: 1145 - 2.3.4.5/24 1146 macaddress: 00:11:22:33:44:77 1147 `[1:] 1148 np := MustNetplanFromYaml(c, input) 1149 1150 device, err := np.FindVLANByMAC("00:11:22:33:44:77") 1151 c.Assert(err, jc.ErrorIsNil) 1152 c.Check(device, gc.Equals, "id0.123") 1153 1154 // This is an Ethernet, not a VLAN 1155 _, err = np.FindVLANByMAC("00:11:22:33:44:55") 1156 c.Check(err, gc.ErrorMatches, `VLAN device with MAC "00:11:22:33:44:55" not found`) 1157 c.Check(err, jc.Satisfies, errors.IsNotFound) 1158 } 1159 1160 func (s *NetplanSuite) TestFindBondByName(c *gc.C) { 1161 input := ` 1162 network: 1163 version: 2 1164 renderer: NetworkManager 1165 ethernets: 1166 eno1: 1167 match: 1168 macaddress: "00:11:22:33:44:55" 1169 set-name: eno1 1170 eno2: 1171 match: 1172 macaddress: "00:11:22:33:44:66" 1173 set-name: eno2 1174 eno3: 1175 match: 1176 macaddress: "00:11:22:33:44:77" 1177 set-name: eno3 1178 eno4: 1179 match: 1180 macaddress: "00:11:22:33:44:88" 1181 set-name: eno4 1182 bonds: 1183 bond0: 1184 interfaces: [eno1, eno2] 1185 bond1: 1186 interfaces: [eno3, eno4] 1187 macaddress: "00:11:22:33:44:77" 1188 parameters: 1189 primary: eno3 1190 `[1:] 1191 np := MustNetplanFromYaml(c, input) 1192 1193 device, err := np.FindBondByName("bond0") 1194 c.Assert(err, jc.ErrorIsNil) 1195 c.Check(device, gc.Equals, "bond0") 1196 1197 device, err = np.FindBondByName("bond1") 1198 c.Assert(err, jc.ErrorIsNil) 1199 c.Check(device, gc.Equals, "bond1") 1200 1201 _, err = np.FindBondByName("bond3") 1202 c.Check(err, gc.ErrorMatches, "bond device with name \"bond3\" not found") 1203 c.Check(err, jc.Satisfies, errors.IsNotFound) 1204 1205 // eno4 is an Ethernet, not a Bond 1206 _, err = np.FindBondByName("eno4") 1207 c.Check(err, gc.ErrorMatches, "bond device with name \"eno4\" not found") 1208 c.Check(err, jc.Satisfies, errors.IsNotFound) 1209 } 1210 1211 func (s *NetplanSuite) TestFindBondByMAC(c *gc.C) { 1212 input := ` 1213 network: 1214 version: 2 1215 renderer: NetworkManager 1216 ethernets: 1217 eno1: 1218 match: 1219 macaddress: "00:11:22:33:44:55" 1220 set-name: eno1 1221 eno2: 1222 match: 1223 macaddress: "00:11:22:33:44:66" 1224 set-name: eno2 1225 eno3: 1226 match: 1227 macaddress: "00:11:22:33:44:77" 1228 set-name: eno3 1229 eno4: 1230 match: 1231 macaddress: "00:11:22:33:44:88" 1232 set-name: eno4 1233 bonds: 1234 bond0: 1235 interfaces: [eno1, eno2] 1236 bond1: 1237 interfaces: [eno3, eno4] 1238 macaddress: "00:11:22:33:44:77" 1239 parameters: 1240 primary: eno3 1241 `[1:] 1242 np := MustNetplanFromYaml(c, input) 1243 1244 device, err := np.FindBondByMAC("00:11:22:33:44:77") 1245 c.Assert(err, jc.ErrorIsNil) 1246 c.Check(device, gc.Equals, "bond1") 1247 1248 _, err = np.FindBondByMAC("00:11:22:33:44:99") 1249 c.Check(err, gc.ErrorMatches, `bond device with MAC "00:11:22:33:44:99" not found`) 1250 c.Check(err, jc.Satisfies, errors.IsNotFound) 1251 1252 // This is an Ethernet, not a Bond 1253 _, err = np.FindBondByMAC("00:11:22:33:44:55") 1254 c.Check(err, gc.ErrorMatches, `bond device with MAC "00:11:22:33:44:55" not found`) 1255 c.Check(err, jc.Satisfies, errors.IsNotFound) 1256 } 1257 1258 func checkFindDevice(c *gc.C, np *netplan.Netplan, name, mac, device string, dtype netplan.DeviceType, expErr string) { 1259 foundDev, foundType, foundErr := np.FindDeviceByNameOrMAC(name, mac) 1260 if expErr != "" { 1261 c.Check(foundErr, gc.ErrorMatches, expErr) 1262 c.Check(foundErr, jc.Satisfies, errors.IsNotFound) 1263 } else { 1264 c.Assert(foundErr, jc.ErrorIsNil) 1265 c.Check(foundDev, gc.Equals, device) 1266 c.Check(foundType, gc.Equals, dtype) 1267 } 1268 } 1269 1270 func (s *NetplanSuite) TestFindDeviceByNameOrMAC(c *gc.C) { 1271 np := MustNetplanFromYaml(c, ` 1272 network: 1273 version: 2 1274 renderer: NetworkManager 1275 ethernets: 1276 id0: 1277 match: 1278 macaddress: "00:11:22:33:44:55" 1279 set-name: id0 1280 id1: 1281 match: 1282 macaddress: de:ad:be:ef:01:02 1283 set-name: id1 1284 eno3: 1285 match: 1286 macaddress: de:ad:be:ef:01:03 1287 set-name: eno3 1288 bonds: 1289 bond0: 1290 interfaces: [id0, id1] 1291 parameters: 1292 mode: 802.3ad 1293 lacp-rate: fast 1294 mii-monitor-interval: 100 1295 transmit-hash-policy: layer2 1296 up-delay: 0 1297 down-delay: 0 1298 vlans: 1299 bond0.209: 1300 id: 209 1301 link: bond0 1302 addresses: 1303 - 123.123.123.123/24 1304 nameservers: 1305 addresses: [8.8.8.8] 1306 eno3.123: 1307 id: 123 1308 link: eno3 1309 macaddress: de:ad:be:ef:01:03 1310 `) 1311 checkFindDevice(c, np, "missing", "", "missing", "", 1312 `device - name "missing" MAC "" not found`) 1313 checkFindDevice(c, np, "missing", "dd:ee:ff:00:11:22", "missing", "", 1314 `device - name "missing" MAC "dd:ee:ff:00:11:22" not found`) 1315 checkFindDevice(c, np, "", "dd:ee:ff:00:11:22", "missing", "", 1316 `device - name "" MAC "dd:ee:ff:00:11:22" not found`) 1317 checkFindDevice(c, np, "eno3", "", "eno3", netplan.TypeEthernet, "") 1318 checkFindDevice(c, np, "eno3", "de:ad:be:ef:01:03", "eno3", netplan.TypeEthernet, "") 1319 checkFindDevice(c, np, "bond0", "", "bond0", netplan.TypeBond, "") 1320 checkFindDevice(c, np, "bond0.209", "", "bond0.209", netplan.TypeVLAN, "") 1321 checkFindDevice(c, np, "eno3.123", "de:ad:be:ef:01:03", "eno3.123", netplan.TypeVLAN, "") 1322 checkFindDevice(c, np, "", "de:ad:be:ef:01:03", "eno3.123", netplan.TypeVLAN, "") 1323 } 1324 1325 func (s *NetplanSuite) TestReadDirectory(c *gc.C) { 1326 expected := ` 1327 network: 1328 version: 2 1329 renderer: NetworkManager 1330 ethernets: 1331 id0: 1332 match: 1333 macaddress: "00:11:22:33:44:55" 1334 set-name: eno1 1335 addresses: 1336 - 1.2.3.4/24 1337 - 2000::1/64 1338 gateway4: 1.2.3.8 1339 gateway6: 2000::2 1340 nameservers: 1341 search: [foo.local, bar.local] 1342 addresses: [8.8.8.8, 1.1.1.1] 1343 id1: 1344 match: 1345 macaddress: 00:11:22:33:44:66 1346 addresses: 1347 - 1.2.4.4/24 1348 - 2001::1/64 1349 gateway4: 1.2.4.5 1350 gateway6: 2001::2 1351 nameservers: 1352 search: [baz.local] 1353 addresses: [8.8.4.4] 1354 id2: 1355 match: 1356 driver: iwldvm 1357 set-name: eno3 1358 bridges: 1359 some-bridge: 1360 interfaces: [id2] 1361 addresses: 1362 - 1.5.6.7/24 1363 `[1:] 1364 np, err := netplan.ReadDirectory("testdata/TestReadDirectory") 1365 c.Assert(err, jc.ErrorIsNil) 1366 1367 out, err := netplan.Marshal(&np) 1368 c.Assert(err, jc.ErrorIsNil) 1369 c.Check(string(out), gc.Equals, expected) 1370 } 1371 1372 func (s *NetplanSuite) TestReadWriteBackupRollback(c *gc.C) { 1373 expected := ` 1374 network: 1375 version: 2 1376 renderer: NetworkManager 1377 ethernets: 1378 eno1: 1379 match: 1380 macaddress: "00:11:22:33:44:55" 1381 set-name: eno1 1382 id1: 1383 match: 1384 macaddress: 00:11:22:33:44:66 1385 addresses: 1386 - 1.2.4.4/24 1387 - 2001::1/64 1388 gateway4: 1.2.4.5 1389 gateway6: 2001::2 1390 nameservers: 1391 search: [baz.local] 1392 addresses: [8.8.4.4] 1393 id2: 1394 match: 1395 driver: iwldvm 1396 bridges: 1397 juju-bridge: 1398 interfaces: [eno1] 1399 addresses: 1400 - 1.2.3.4/24 1401 - 2000::1/64 1402 gateway4: 1.2.3.5 1403 gateway6: 2000::2 1404 nameservers: 1405 search: [foo.local, bar.local] 1406 addresses: [8.8.8.8] 1407 some-bridge: 1408 interfaces: [id2] 1409 addresses: 1410 - 1.5.6.7/24 1411 vlans: 1412 eno1.123: 1413 id: 123 1414 link: eno1 1415 macaddress: "00:11:22:33:44:55" 1416 `[1:] 1417 tempDir := c.MkDir() 1418 files := []string{"00.yaml", "01.yaml"} 1419 contents := make([][]byte, len(files)) 1420 for i, file := range files { 1421 var err error 1422 contents[i], err = os.ReadFile(path.Join("testdata/TestReadWriteBackup", file)) 1423 c.Assert(err, jc.ErrorIsNil) 1424 err = os.WriteFile(path.Join(tempDir, file), contents[i], 0644) 1425 c.Assert(err, jc.ErrorIsNil) 1426 } 1427 np, err := netplan.ReadDirectory(tempDir) 1428 c.Assert(err, jc.ErrorIsNil) 1429 1430 err = np.BridgeEthernetById("eno1", "juju-bridge") 1431 c.Assert(err, jc.ErrorIsNil) 1432 1433 generatedFile, err := np.Write("") 1434 c.Assert(err, jc.ErrorIsNil) 1435 1436 _, err = np.Write("") 1437 c.Check(err, gc.ErrorMatches, "Cannot write the same netplan twice") 1438 1439 err = np.MoveYamlsToBak() 1440 c.Assert(err, jc.ErrorIsNil) 1441 1442 err = np.MoveYamlsToBak() 1443 c.Check(err, gc.ErrorMatches, "Cannot backup netplan yamls twice") 1444 1445 dirEntries, err := os.ReadDir(tempDir) 1446 c.Assert(err, jc.ErrorIsNil) 1447 c.Check(dirEntries, gc.HasLen, len(files)+1) 1448 for _, entry := range dirEntries { 1449 for i, fileName := range files { 1450 // original file is moved to backup 1451 c.Check(entry.Name(), gc.Not(gc.Equals), fileName) 1452 // backup file has the proper content 1453 if strings.HasPrefix(entry.Name(), fmt.Sprintf("%s.bak.", fileName)) { 1454 data, err := os.ReadFile(path.Join(tempDir, entry.Name())) 1455 c.Assert(err, jc.ErrorIsNil) 1456 c.Check(data, gc.DeepEquals, contents[i]) 1457 } 1458 } 1459 } 1460 1461 data, err := os.ReadFile(generatedFile) 1462 c.Assert(err, jc.ErrorIsNil) 1463 c.Check(string(data), gc.Equals, expected) 1464 1465 err = np.Rollback() 1466 c.Assert(err, jc.ErrorIsNil) 1467 1468 dirEntries, err = os.ReadDir(tempDir) 1469 c.Assert(err, jc.ErrorIsNil) 1470 c.Check(dirEntries, gc.HasLen, len(files)) 1471 foundFiles := 0 1472 for _, entry := range dirEntries { 1473 for i, fileName := range files { 1474 if entry.Name() == fileName { 1475 data, err := os.ReadFile(path.Join(tempDir, entry.Name())) 1476 c.Assert(err, jc.ErrorIsNil) 1477 c.Check(data, gc.DeepEquals, contents[i]) 1478 foundFiles++ 1479 } 1480 } 1481 } 1482 c.Check(foundFiles, gc.Equals, len(files)) 1483 1484 // After rollback we should be able to write and move yamls to backup again 1485 // We also check if writing to an explicit file works 1486 myPath := path.Join(tempDir, "my-own-path.yaml") 1487 outPath, err := np.Write(myPath) 1488 c.Assert(err, jc.ErrorIsNil) 1489 c.Check(outPath, gc.Equals, myPath) 1490 data, err = os.ReadFile(outPath) 1491 c.Assert(err, jc.ErrorIsNil) 1492 c.Check(string(data), gc.Equals, expected) 1493 1494 err = np.MoveYamlsToBak() 1495 c.Assert(err, jc.ErrorIsNil) 1496 } 1497 1498 func (s *NetplanSuite) TestReadDirectoryMissing(c *gc.C) { 1499 coretesting.SkipIfWindowsBug(c, "lp:1771077") 1500 // On Windows the error is something like: "The system cannot find the file specified" 1501 tempDir := c.MkDir() 1502 os.RemoveAll(tempDir) 1503 _, err := netplan.ReadDirectory(tempDir) 1504 c.Check(err, gc.ErrorMatches, ".*open .* no such file or directory") 1505 } 1506 1507 func (s *NetplanSuite) TestReadDirectoryAccessDenied(c *gc.C) { 1508 coretesting.SkipIfWindowsBug(c, "lp:1771077") 1509 tempDir := c.MkDir() 1510 err := os.WriteFile(path.Join(tempDir, "00-file.yaml"), []byte("network:\n"), 00000) 1511 c.Assert(err, jc.ErrorIsNil) 1512 _, err = netplan.ReadDirectory(tempDir) 1513 c.Check(err, gc.ErrorMatches, ".*open .*/00-file.yaml: permission denied") 1514 } 1515 1516 func (s *NetplanSuite) TestReadDirectoryBrokenYaml(c *gc.C) { 1517 tempDir := c.MkDir() 1518 err := os.WriteFile(path.Join(tempDir, "00-file.yaml"), []byte("I am not a yaml file!\nreally!\n"), 0644) 1519 c.Assert(err, jc.ErrorIsNil) 1520 _, err = netplan.ReadDirectory(tempDir) 1521 c.Check(err, gc.ErrorMatches, ".*yaml: unmarshal errors:\n.*") 1522 } 1523 1524 func (s *NetplanSuite) TestWritePermissionDenied(c *gc.C) { 1525 coretesting.SkipIfWindowsBug(c, "lp:1771077") 1526 tempDir := c.MkDir() 1527 np, err := netplan.ReadDirectory(tempDir) 1528 c.Assert(err, jc.ErrorIsNil) 1529 os.Chmod(tempDir, 00000) 1530 _, err = np.Write(path.Join(tempDir, "99-juju-netplan.yaml")) 1531 c.Check(err, gc.ErrorMatches, ".*open .* permission denied") 1532 } 1533 1534 func (s *NetplanSuite) TestWriteCantGenerateName(c *gc.C) { 1535 tempDir := c.MkDir() 1536 for i := 0; i < 100; i++ { 1537 filePath := path.Join(tempDir, fmt.Sprintf("%0.2d-juju.yaml", i)) 1538 os.WriteFile(filePath, []byte{}, 0644) 1539 } 1540 np, err := netplan.ReadDirectory(tempDir) 1541 c.Assert(err, jc.ErrorIsNil) 1542 _, err = np.Write("") 1543 c.Check(err, gc.ErrorMatches, "Can't generate a filename for netplan YAML") 1544 } 1545 1546 func (s *NetplanSuite) TestProperReadingOrder(c *gc.C) { 1547 var header = ` 1548 network: 1549 version: 2 1550 renderer: NetworkManager 1551 ethernets: 1552 `[1:] 1553 var template = ` 1554 id%d: 1555 set-name: foo.%d.%d 1556 `[1:] 1557 tempDir := c.MkDir() 1558 1559 for _, n := range rand.Perm(100) { 1560 content := header 1561 for i := 0; i < (100 - n); i++ { 1562 content += fmt.Sprintf(template, i, i, n) 1563 } 1564 os.WriteFile(path.Join(tempDir, fmt.Sprintf("%0.2d-test.yaml", n)), []byte(content), 0644) 1565 } 1566 1567 np, err := netplan.ReadDirectory(tempDir) 1568 c.Assert(err, jc.ErrorIsNil) 1569 1570 fileName, err := np.Write("") 1571 c.Assert(err, jc.ErrorIsNil) 1572 1573 writtenContent, err := os.ReadFile(fileName) 1574 c.Assert(err, jc.ErrorIsNil) 1575 1576 content := header 1577 for n := 0; n < 100; n++ { 1578 content += fmt.Sprintf(template, n, n, 100-n-1) 1579 } 1580 c.Check(string(writtenContent), gc.Equals, content) 1581 } 1582 1583 type Example struct { 1584 filename string 1585 content string 1586 } 1587 1588 func readExampleStrings(c *gc.C) []Example { 1589 dirEntries, err := os.ReadDir("testdata/examples") 1590 c.Assert(err, jc.ErrorIsNil) 1591 var examples []Example 1592 for _, entry := range dirEntries { 1593 if entry.IsDir() { 1594 continue 1595 } 1596 if strings.HasSuffix(entry.Name(), ".yaml") { 1597 f, err := os.Open("testdata/examples/" + entry.Name()) 1598 c.Assert(err, jc.ErrorIsNil) 1599 content, err := io.ReadAll(f) 1600 f.Close() 1601 c.Assert(err, jc.ErrorIsNil) 1602 examples = append(examples, Example{ 1603 filename: entry.Name(), 1604 content: string(content), 1605 }) 1606 } 1607 } 1608 // Make sure we find all the example files, if we change the count, update this number, but we don't allow the test 1609 // suite to find the wrong number of files. 1610 c.Assert(len(examples), gc.Equals, 12) 1611 return examples 1612 } 1613 1614 func (s *NetplanSuite) TestNetplanExamples(c *gc.C) { 1615 // these are the examples shipped by netplan, we should be able to read all of them 1616 examples := readExampleStrings(c) 1617 for _, example := range examples { 1618 c.Logf("example: %s", example.filename) 1619 var orig map[interface{}]interface{} 1620 err := yaml.UnmarshalStrict([]byte(example.content), &orig) 1621 c.Assert(err, jc.ErrorIsNil, gc.Commentf("failed to unmarshal as map %s", example.filename)) 1622 np := MustNetplanFromYaml(c, example.content) 1623 // We don't assert that we exactly match the serialized form (we may output fields in a different order), 1624 // but we do check that if we Marshal and then Unmarshal again, we get the same map contents. 1625 // (We might also change boolean 'no' to 'false', etc. 1626 out, err := netplan.Marshal(np) 1627 c.Check(err, jc.ErrorIsNil, gc.Commentf("failed to marshal %s", example.filename)) 1628 var roundtripped map[interface{}]interface{} 1629 err = yaml.UnmarshalStrict(out, &roundtripped) 1630 c.Assert(err, jc.ErrorIsNil) 1631 if !reflect.DeepEqual(orig, roundtripped) { 1632 pretty.Ldiff(c, orig, roundtripped) 1633 c.Errorf("marshalling and unmarshalling %s did not contain the same content", example.filename) 1634 } 1635 } 1636 }