github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/network/netplan/netplan.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package netplan
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"sort"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/juju/errors"
    17  	goyaml "gopkg.in/yaml.v2"
    18  )
    19  
    20  // Representation of netplan YAML format as Go structures
    21  // The order of fields is consistent with Netplan docs
    22  type Nameservers struct {
    23  	Search    []string `yaml:"search,omitempty,flow"`
    24  	Addresses []string `yaml:"addresses,omitempty,flow"`
    25  }
    26  
    27  // Interface includes all the fields that are common between all interfaces (ethernet, wifi, bridge, bond)
    28  type Interface struct {
    29  	AcceptRA  *bool    `yaml:"accept-ra,omitempty"`
    30  	Addresses []string `yaml:"addresses,omitempty"`
    31  	// Critical doesn't have to be *bool because it is only used if True
    32  	Critical bool `yaml:"critical,omitempty"`
    33  	// DHCP4 defaults to true, so we must use a pointer to know if it was specified as false
    34  	DHCP4          *bool         `yaml:"dhcp4,omitempty"`
    35  	DHCP6          *bool         `yaml:"dhcp6,omitempty"`
    36  	DHCPIdentifier string        `yaml:"dhcp-identifier,omitempty"` // "duid" or  "mac"
    37  	Gateway4       string        `yaml:"gateway4,omitempty"`
    38  	Gateway6       string        `yaml:"gateway6,omitempty"`
    39  	Nameservers    Nameservers   `yaml:"nameservers,omitempty"`
    40  	MACAddress     string        `yaml:"macaddress,omitempty"`
    41  	MTU            int           `yaml:"mtu,omitempty"`
    42  	Renderer       string        `yaml:"renderer,omitempty"` // NetworkManager or networkd
    43  	Routes         []Route       `yaml:"routes,omitempty"`
    44  	RoutingPolicy  []RoutePolicy `yaml:"routing-policy,omitempty"`
    45  	// Optional doesn't have to be *bool because it is only used if True
    46  	Optional bool `yaml:"optional,omitempty"`
    47  }
    48  
    49  // Ethernet defines fields for just Ethernet devices
    50  type Ethernet struct {
    51  	Match     map[string]string `yaml:"match,omitempty"`
    52  	Wakeonlan bool              `yaml:"wakeonlan,omitempty"`
    53  	SetName   string            `yaml:"set-name,omitempty"`
    54  	Interface `yaml:",inline"`
    55  }
    56  type AccessPoint struct {
    57  	Password string `yaml:"password,omitempty"`
    58  	Mode     string `yaml:"mode,omitempty"`
    59  	Channel  int    `yaml:"channel,omitempty"`
    60  }
    61  type Wifi struct {
    62  	Match        map[string]string      `yaml:"match,omitempty"`
    63  	SetName      string                 `yaml:"set-name,omitempty"`
    64  	Wakeonlan    bool                   `yaml:"wakeonlan,omitempty"`
    65  	AccessPoints map[string]AccessPoint `yaml:"access-points,omitempty"`
    66  	Interface    `yaml:",inline"`
    67  }
    68  
    69  type BridgeParameters struct {
    70  	AgeingTime   *int           `yaml:"ageing-time,omitempty"`
    71  	ForwardDelay *int           `yaml:"forward-delay,omitempty"`
    72  	HelloTime    *int           `yaml:"hello-time,omitempty"`
    73  	MaxAge       *int           `yaml:"max-age,omitempty"`
    74  	PathCost     map[string]int `yaml:"path-cost,omitempty"`
    75  	PortPriority map[string]int `yaml:"port-priority,omitempty"`
    76  	Priority     *int           `yaml:"priority,omitempty"`
    77  	STP          *bool          `yaml:"stp,omitempty"`
    78  }
    79  
    80  type Bridge struct {
    81  	Interfaces []string `yaml:"interfaces,omitempty,flow"`
    82  	Interface  `yaml:",inline"`
    83  	Parameters BridgeParameters `yaml:"parameters,omitempty"`
    84  }
    85  
    86  type Route struct {
    87  	From   string `yaml:"from,omitempty"`
    88  	OnLink *bool  `yaml:"on-link,omitempty"`
    89  	Scope  string `yaml:"scope,omitempty"`
    90  	Table  *int   `yaml:"table,omitempty"`
    91  	To     string `yaml:"to,omitempty"`
    92  	Type   string `yaml:"type,omitempty"`
    93  	Via    string `yaml:"via,omitempty"`
    94  	Metric *int   `yaml:"metric,omitempty"`
    95  }
    96  
    97  type RoutePolicy struct {
    98  	From          string `yaml:"from,omitempty"`
    99  	Mark          *int   `yaml:"mark,omitempty"`
   100  	Priority      *int   `yaml:"priority,omitempty"`
   101  	Table         *int   `yaml:"table,omitempty"`
   102  	To            string `yaml:"to,omitempty"`
   103  	TypeOfService *int   `yaml:"type-of-service,omitempty"`
   104  }
   105  
   106  type Network struct {
   107  	Version   int                 `yaml:"version"`
   108  	Renderer  string              `yaml:"renderer,omitempty"`
   109  	Ethernets map[string]Ethernet `yaml:"ethernets,omitempty"`
   110  	Wifis     map[string]Wifi     `yaml:"wifis,omitempty"`
   111  	Bridges   map[string]Bridge   `yaml:"bridges,omitempty"`
   112  	Bonds     map[string]Bond     `yaml:"bonds,omitempty"`
   113  	VLANs     map[string]VLAN     `yaml:"vlans,omitempty"`
   114  	Routes    []Route             `yaml:"routes,omitempty"`
   115  }
   116  
   117  type Netplan struct {
   118  	Network         Network `yaml:"network"`
   119  	sourceDirectory string
   120  	sourceFiles     []string
   121  	backedFiles     map[string]string
   122  	writtenFile     string
   123  }
   124  
   125  // VLAN represents the structures for defining VLAN sections
   126  type VLAN struct {
   127  	Id        *int   `yaml:"id,omitempty"`
   128  	Link      string `yaml:"link,omitempty"`
   129  	Interface `yaml:",inline"`
   130  }
   131  
   132  // Bond is the interface definition of the bonds: section of netplan
   133  type Bond struct {
   134  	Interfaces []string `yaml:"interfaces,omitempty,flow"`
   135  	Interface  `yaml:",inline"`
   136  	Parameters BondParameters `yaml:"parameters,omitempty"`
   137  }
   138  
   139  // IntString is used to specialize values that can be integers or strings
   140  type IntString struct {
   141  	Int    *int
   142  	String *string
   143  }
   144  
   145  func (i *IntString) UnmarshalYAML(unmarshal func(interface{}) error) error {
   146  	var asInt int
   147  	var err error
   148  	if err = unmarshal(&asInt); err == nil {
   149  		i.Int = &asInt
   150  		return nil
   151  	}
   152  	var asString string
   153  	if err = unmarshal(&asString); err == nil {
   154  		i.String = &asString
   155  		return nil
   156  	}
   157  	return errors.Annotatef(err, "not valid as an int or a string")
   158  }
   159  
   160  func (i IntString) MarshalYAML() (interface{}, error) {
   161  	if i.Int != nil {
   162  		return *i.Int, nil
   163  	} else if i.String != nil {
   164  		return *i.String, nil
   165  	}
   166  	return nil, nil
   167  }
   168  
   169  // For a definition of what netplan supports see here:
   170  // https://github.com/CanonicalLtd/netplan/blob/7afef6af053794a400d96f89a81c938c08420783/src/parse.c#L1180
   171  // For a definition of what the parameters mean or what values they can contain, see here:
   172  // https://www.kernel.org/doc/Documentation/networking/bonding.txt
   173  // Note that most parameters can be specified as integers or as strings, which you need to be careful with YAML
   174  // as it defaults to strongly typing them.
   175  // TODO: (jam 2018-05-14) Should we be sorting the attributes alphabetically?
   176  type BondParameters struct {
   177  	Mode               IntString `yaml:"mode,omitempty"`
   178  	LACPRate           IntString `yaml:"lacp-rate,omitempty"`
   179  	MIIMonitorInterval *int      `yaml:"mii-monitor-interval,omitempty"`
   180  	MinLinks           *int      `yaml:"min-links,omitempty"`
   181  	TransmitHashPolicy string    `yaml:"transmit-hash-policy,omitempty"`
   182  	ADSelect           IntString `yaml:"ad-select,omitempty"`
   183  	AllSlavesActive    *bool     `yaml:"all-slaves-active,omitempty"`
   184  	ARPInterval        *int      `yaml:"arp-interval,omitempty"`
   185  	ARPIPTargets       []string  `yaml:"arp-ip-targets,omitempty"`
   186  	ARPValidate        IntString `yaml:"arp-validate,omitempty"`
   187  	ARPAllTargets      IntString `yaml:"arp-all-targets,omitempty"`
   188  	UpDelay            *int      `yaml:"up-delay,omitempty"`
   189  	DownDelay          *int      `yaml:"down-delay,omitempty"`
   190  	FailOverMACPolicy  IntString `yaml:"fail-over-mac-policy,omitempty"`
   191  	// Netplan misspelled this as 'gratuitious-arp', not sure if it works with that name.
   192  	// We may need custom handling of both spellings.
   193  	GratuitousARP         *int      `yaml:"gratuitious-arp,omitempty"` // nolint: misspell
   194  	PacketsPerSlave       *int      `yaml:"packets-per-slave,omitempty"`
   195  	PrimaryReselectPolicy IntString `yaml:"primary-reselect-policy,omitempty"`
   196  	ResendIGMP            *int      `yaml:"resend-igmp,omitempty"`
   197  	// bonding.txt says that this can be a value from 1-0x7fffffff, should we be forcing it to be a hex value?
   198  	LearnPacketInterval *int   `yaml:"learn-packet-interval,omitempty"`
   199  	Primary             string `yaml:"primary,omitempty"`
   200  }
   201  
   202  // BridgeEthernetById takes a deviceId and creates a bridge with this device
   203  // using this devices config
   204  func (np *Netplan) BridgeEthernetById(deviceId string, bridgeName string) (err error) {
   205  	ethernet, ok := np.Network.Ethernets[deviceId]
   206  	if !ok {
   207  		return errors.NotFoundf("ethernet device with id %q for bridge %q", deviceId, bridgeName)
   208  	}
   209  	shouldCreate, err := np.shouldCreateBridge(deviceId, bridgeName)
   210  	if !shouldCreate {
   211  		// err may be nil, but we shouldn't continue creating
   212  		return errors.Trace(err)
   213  	}
   214  	np.createBridgeFromInterface(bridgeName, deviceId, &ethernet.Interface)
   215  	np.Network.Ethernets[deviceId] = ethernet
   216  	return nil
   217  }
   218  
   219  // BridgeVLANById takes a deviceId and creates a bridge with this device
   220  // using this devices config
   221  func (np *Netplan) BridgeVLANById(deviceId string, bridgeName string) (err error) {
   222  	vlan, ok := np.Network.VLANs[deviceId]
   223  	if !ok {
   224  		return errors.NotFoundf("VLAN device with id %q for bridge %q", deviceId, bridgeName)
   225  	}
   226  	shouldCreate, err := np.shouldCreateBridge(deviceId, bridgeName)
   227  	if !shouldCreate {
   228  		// err may be nil, but we shouldn't continue creating
   229  		return errors.Trace(err)
   230  	}
   231  	np.createBridgeFromInterface(bridgeName, deviceId, &vlan.Interface)
   232  	np.Network.VLANs[deviceId] = vlan
   233  	return nil
   234  }
   235  
   236  // BridgeBondById takes a deviceId and creates a bridge with this device
   237  // using this devices config
   238  func (np *Netplan) BridgeBondById(deviceId string, bridgeName string) (err error) {
   239  	bond, ok := np.Network.Bonds[deviceId]
   240  	if !ok {
   241  		return errors.NotFoundf("bond device with id %q for bridge %q", deviceId, bridgeName)
   242  	}
   243  	shouldCreate, err := np.shouldCreateBridge(deviceId, bridgeName)
   244  	if !shouldCreate {
   245  		// err may be nil, but we shouldn't continue creating
   246  		return errors.Trace(err)
   247  	}
   248  	np.createBridgeFromInterface(bridgeName, deviceId, &bond.Interface)
   249  	np.Network.Bonds[deviceId] = bond
   250  	return nil
   251  }
   252  
   253  // shouldCreateBridge returns true only if it is clear the bridge doesn't already exist, and that the existing device
   254  // isn't in a different bridge.
   255  func (np *Netplan) shouldCreateBridge(deviceId string, bridgeName string) (bool, error) {
   256  	for bName, bridge := range np.Network.Bridges {
   257  		for _, i := range bridge.Interfaces {
   258  			if i == deviceId {
   259  				// The device is already properly bridged, nothing to do
   260  				if bridgeName == bName {
   261  					return false, nil
   262  				} else {
   263  					return false, errors.AlreadyExistsf("cannot create bridge %q, device %q in bridge %q", bridgeName, deviceId, bName)
   264  				}
   265  			}
   266  		}
   267  		if bridgeName == bName {
   268  			return false, errors.AlreadyExistsf(
   269  				"cannot create bridge %q with device %q - bridge %q w/ interfaces %q",
   270  				bridgeName, deviceId, bridgeName, strings.Join(bridge.Interfaces, ", "))
   271  		}
   272  	}
   273  	return true, nil
   274  }
   275  
   276  // createBridgeFromInterface will create a bridge stealing the interface details, and wiping the existing interface
   277  // except for MTU so that IP Address information is never duplicated.
   278  func (np *Netplan) createBridgeFromInterface(bridgeName, deviceId string, intf *Interface) {
   279  	if np.Network.Bridges == nil {
   280  		np.Network.Bridges = make(map[string]Bridge)
   281  	}
   282  	np.Network.Bridges[bridgeName] = Bridge{
   283  		Interfaces: []string{deviceId},
   284  		Interface:  *intf,
   285  	}
   286  	*intf = Interface{MTU: intf.MTU}
   287  }
   288  
   289  func (np *Netplan) merge(other *Netplan) {
   290  	// Only copy attributes that would be unmarshalled from yaml.
   291  	// This blithely replaces keys in the maps (eg. Ethernets or
   292  	// Wifis) if they're set in both np and other - it's not clear
   293  	// from the reference whether this is the right thing to do.
   294  	// See https://bugs.launchpad.net/juju/+bug/1701429 and
   295  	// https://netplan.io/reference#general-structure
   296  	np.Network.Version = other.Network.Version
   297  	np.Network.Renderer = other.Network.Renderer
   298  	np.Network.Routes = other.Network.Routes
   299  	if np.Network.Ethernets == nil {
   300  		np.Network.Ethernets = other.Network.Ethernets
   301  	} else {
   302  		for key, val := range other.Network.Ethernets {
   303  			np.Network.Ethernets[key] = val
   304  		}
   305  	}
   306  	if np.Network.Wifis == nil {
   307  		np.Network.Wifis = other.Network.Wifis
   308  	} else {
   309  		for key, val := range other.Network.Wifis {
   310  			np.Network.Wifis[key] = val
   311  		}
   312  	}
   313  	if np.Network.Bridges == nil {
   314  		np.Network.Bridges = other.Network.Bridges
   315  	} else {
   316  		for key, val := range other.Network.Bridges {
   317  			np.Network.Bridges[key] = val
   318  		}
   319  	}
   320  	if np.Network.Bonds == nil {
   321  		np.Network.Bonds = other.Network.Bonds
   322  	} else {
   323  		for key, val := range other.Network.Bonds {
   324  			np.Network.Bonds[key] = val
   325  		}
   326  	}
   327  	if np.Network.VLANs == nil {
   328  		np.Network.VLANs = other.Network.VLANs
   329  	} else {
   330  		for key, val := range other.Network.VLANs {
   331  			np.Network.VLANs[key] = val
   332  		}
   333  	}
   334  }
   335  
   336  func Unmarshal(in []byte, out *Netplan) error {
   337  	if out == nil {
   338  		return errors.NotValidf("nil out Netplan")
   339  	}
   340  	// Use UnmarshalStrict because we want errors for unknown
   341  	// attributes. This also refuses to overwrite keys (which we need)
   342  	// so unmarshal locally and copy across.
   343  	var local Netplan
   344  	if err := goyaml.UnmarshalStrict(in, &local); err != nil {
   345  		return errors.Trace(err)
   346  	}
   347  	out.merge(&local)
   348  	return nil
   349  }
   350  
   351  func Marshal(in *Netplan) (out []byte, err error) {
   352  	return goyaml.Marshal(in)
   353  }
   354  
   355  // readYamlFile reads netplan yaml into existing netplan structure
   356  // TODO(wpk) 2017-06-14 When reading files sequentially netplan replaces single
   357  // keys with new values, we have to simulate this behaviour.
   358  // https://bugs.launchpad.net/juju/+bug/1701429
   359  func (np *Netplan) readYamlFile(path string) (err error) {
   360  	contents, err := ioutil.ReadFile(path)
   361  	if err != nil {
   362  		return err
   363  	}
   364  	err = Unmarshal(contents, np)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	return nil
   370  }
   371  
   372  type sortableFileInfos []os.FileInfo
   373  
   374  func (fil sortableFileInfos) Len() int {
   375  	return len(fil)
   376  }
   377  
   378  func (fil sortableFileInfos) Less(i, j int) bool {
   379  	return fil[i].Name() < fil[j].Name()
   380  }
   381  
   382  func (fil sortableFileInfos) Swap(i, j int) {
   383  	fil[i], fil[j] = fil[j], fil[i]
   384  }
   385  
   386  // ReadDirectory reads the contents of a netplan directory and
   387  // returns complete config.
   388  func ReadDirectory(dirPath string) (np Netplan, err error) {
   389  	fileInfos, err := ioutil.ReadDir(dirPath)
   390  	if err != nil {
   391  		return np, err
   392  	}
   393  	np.sourceDirectory = dirPath
   394  	sortedFileInfos := sortableFileInfos(fileInfos)
   395  	sort.Sort(sortedFileInfos)
   396  	for _, fileInfo := range sortedFileInfos {
   397  		if !fileInfo.IsDir() && strings.HasSuffix(fileInfo.Name(), ".yaml") {
   398  			np.sourceFiles = append(np.sourceFiles, fileInfo.Name())
   399  		}
   400  	}
   401  	for _, fileName := range np.sourceFiles {
   402  		err := np.readYamlFile(path.Join(np.sourceDirectory, fileName))
   403  		if err != nil {
   404  			return np, err
   405  		}
   406  	}
   407  	return np, nil
   408  }
   409  
   410  // MoveYamlsToBak moves source .yaml files in a directory to .yaml.bak.(timestamp), except
   411  func (np *Netplan) MoveYamlsToBak() (err error) {
   412  	if np.backedFiles != nil {
   413  		return errors.Errorf("Cannot backup netplan yamls twice")
   414  	}
   415  	suffix := fmt.Sprintf(".bak.%d", time.Now().Unix())
   416  	np.backedFiles = make(map[string]string)
   417  	for _, file := range np.sourceFiles {
   418  		newFilename := fmt.Sprintf("%s%s", file, suffix)
   419  		oldFile := path.Join(np.sourceDirectory, file)
   420  		newFile := path.Join(np.sourceDirectory, newFilename)
   421  		err = os.Rename(oldFile, newFile)
   422  		if err != nil {
   423  			logger.Errorf("Cannot rename %s to %s - %q", oldFile, newFile, err.Error())
   424  		}
   425  		np.backedFiles[oldFile] = newFile
   426  	}
   427  	return nil
   428  }
   429  
   430  // Write writes merged netplan yaml to file specified by path. If path is empty filename is autogenerated
   431  func (np *Netplan) Write(inPath string) (filePath string, err error) {
   432  	if np.writtenFile != "" {
   433  		return "", errors.Errorf("Cannot write the same netplan twice")
   434  	}
   435  	if inPath == "" {
   436  		i := 99
   437  		for ; i > 0; i-- {
   438  			filePath = path.Join(np.sourceDirectory, fmt.Sprintf("%0.2d-juju.yaml", i))
   439  			_, err = os.Stat(filePath)
   440  			if os.IsNotExist(err) {
   441  				break
   442  			}
   443  		}
   444  		if i == 0 {
   445  			return "", errors.Errorf("Can't generate a filename for netplan YAML")
   446  		}
   447  	} else {
   448  		filePath = inPath
   449  	}
   450  	tmpFilePath := fmt.Sprintf("%s.tmp.%d", filePath, time.Now().UnixNano())
   451  	out, err := Marshal(np)
   452  	if err != nil {
   453  		return "", err
   454  	}
   455  	err = ioutil.WriteFile(tmpFilePath, out, 0644)
   456  	if err != nil {
   457  		return "", err
   458  	}
   459  	err = os.Rename(tmpFilePath, filePath)
   460  	if err != nil {
   461  		return "", err
   462  	}
   463  	np.writtenFile = filePath
   464  	return filePath, nil
   465  }
   466  
   467  // Rollback moves backed up files to original locations and removes written file
   468  func (np *Netplan) Rollback() (err error) {
   469  	if np.writtenFile != "" {
   470  		os.Remove(np.writtenFile)
   471  	}
   472  	for oldFile, newFile := range np.backedFiles {
   473  		err = os.Rename(newFile, oldFile)
   474  		if err != nil {
   475  			logger.Errorf("Cannot rename %s to %s - %q", newFile, oldFile, err.Error())
   476  		}
   477  	}
   478  	np.backedFiles = nil
   479  	np.writtenFile = ""
   480  	return nil
   481  }
   482  
   483  func (np *Netplan) FindEthernetByMAC(mac string) (device string, err error) {
   484  	for id, ethernet := range np.Network.Ethernets {
   485  		if v, ok := ethernet.Match["macaddress"]; ok && v == mac {
   486  			return id, nil
   487  		}
   488  		if ethernet.MACAddress == mac {
   489  			return id, nil
   490  		}
   491  	}
   492  	return "", errors.NotFoundf("Ethernet device with MAC %q", mac)
   493  }
   494  
   495  func (np *Netplan) FindEthernetByName(name string) (device string, err error) {
   496  	for id, ethernet := range np.Network.Ethernets {
   497  		if matchName, ok := ethernet.Match["name"]; ok {
   498  			// Netplan uses simple wildcards for name matching - so does filepath.Match
   499  			if match, err := filepath.Match(matchName, name); err == nil && match {
   500  				return id, nil
   501  			}
   502  		}
   503  		if ethernet.SetName == name {
   504  			return id, nil
   505  		}
   506  	}
   507  	if _, ok := np.Network.Ethernets[name]; ok {
   508  		return name, nil
   509  	}
   510  	return "", errors.NotFoundf("Ethernet device with name %q", name)
   511  }
   512  
   513  func (np *Netplan) FindVLANByMAC(mac string) (device string, err error) {
   514  	for id, vlan := range np.Network.VLANs {
   515  		if vlan.MACAddress == mac {
   516  			return id, nil
   517  		}
   518  	}
   519  	return "", errors.NotFoundf("VLAN device with MAC %q", mac)
   520  }
   521  
   522  func (np *Netplan) FindVLANByName(name string) (device string, err error) {
   523  	if _, ok := np.Network.VLANs[name]; ok {
   524  		return name, nil
   525  	}
   526  	return "", errors.NotFoundf("VLAN device with name %q", name)
   527  }
   528  
   529  func (np *Netplan) FindBondByMAC(mac string) (device string, err error) {
   530  	for id, bonds := range np.Network.Bonds {
   531  		if bonds.MACAddress == mac {
   532  			return id, nil
   533  		}
   534  	}
   535  	return "", errors.NotFoundf("bond device with MAC %q", mac)
   536  }
   537  
   538  func (np *Netplan) FindBondByName(name string) (device string, err error) {
   539  	if _, ok := np.Network.Bonds[name]; ok {
   540  		return name, nil
   541  	}
   542  	return "", errors.NotFoundf("bond device with name %q", name)
   543  }
   544  
   545  type DeviceType string
   546  
   547  const (
   548  	TypeEthernet = DeviceType("ethernet")
   549  	TypeVLAN     = DeviceType("vlan")
   550  	TypeBond     = DeviceType("bond")
   551  )
   552  
   553  // FindDeviceByMACOrName will look for an Ethernet, VLAN or Bond matching the Name of the device or its MAC address.
   554  // Name is preferred to MAC address.
   555  func (np *Netplan) FindDeviceByNameOrMAC(name, mac string) (string, DeviceType, error) {
   556  	if name != "" {
   557  		bond, err := np.FindBondByName(name)
   558  		if err == nil {
   559  			return bond, TypeBond, nil
   560  		}
   561  		if !errors.IsNotFound(err) {
   562  			return "", "", errors.Trace(err)
   563  		}
   564  		vlan, err := np.FindVLANByName(name)
   565  		if err == nil {
   566  			return vlan, TypeVLAN, nil
   567  		}
   568  		ethernet, err := np.FindEthernetByName(name)
   569  		if err == nil {
   570  			return ethernet, TypeEthernet, nil
   571  		}
   572  
   573  	}
   574  	// by MAC is less reliable because things like vlans often have the same MAC address
   575  	if mac != "" {
   576  		bond, err := np.FindBondByMAC(mac)
   577  		if err == nil {
   578  			return bond, TypeBond, nil
   579  		}
   580  		if !errors.IsNotFound(err) {
   581  			return "", "", errors.Trace(err)
   582  		}
   583  		vlan, err := np.FindVLANByMAC(mac)
   584  		if err == nil {
   585  			return vlan, TypeVLAN, nil
   586  		}
   587  		ethernet, err := np.FindEthernetByMAC(mac)
   588  		if err == nil {
   589  			return ethernet, TypeEthernet, nil
   590  		}
   591  	}
   592  	return "", "", errors.NotFoundf("device - name %q MAC %q", name, mac)
   593  }