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