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  }