github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/maas/devices.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package maas
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/url"
    10  	"path"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/gomaasapi"
    16  
    17  	"github.com/juju/juju/core/instance"
    18  	"github.com/juju/juju/network"
    19  )
    20  
    21  // TODO(dimitern): The types below should be part of gomaasapi.
    22  // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119310616
    23  
    24  type maasZone struct {
    25  	Name        string `json:"name"`
    26  	Description string `json:"description"`
    27  	ResourceURI string `json:"resource_uri"`
    28  }
    29  
    30  type maasMACAddress struct {
    31  	MACAddress string `json:"mac_address"`
    32  }
    33  
    34  type maasDevice struct {
    35  	SystemID      string           `json:"system_id"`
    36  	Parent        string           `json:"parent"`
    37  	Hostname      string           `json:"hostname"`
    38  	IPAddresses   []string         `json:"ip_addresses"`
    39  	Owner         string           `json:"owner"`
    40  	Zone          maasZone         `json:"zone"`
    41  	MACAddressSet []maasMACAddress `json:"macaddress_set"`
    42  	TagNames      []string         `json:"tag_names"`
    43  	ResourceURI   string           `json:"resource_uri"`
    44  }
    45  
    46  func parseDevice(jsonBytes []byte) (*maasDevice, error) {
    47  	var device maasDevice
    48  	if err := json.Unmarshal(jsonBytes, &device); err != nil {
    49  		return nil, errors.Annotate(err, "parsing device")
    50  	}
    51  	return &device, nil
    52  }
    53  
    54  func getJSONBytes(object json.Marshaler) ([]byte, error) {
    55  	rawBytes, err := object.MarshalJSON()
    56  	if err != nil {
    57  		return nil, errors.Annotate(err, "cannot get JSON bytes")
    58  	}
    59  	return rawBytes, nil
    60  }
    61  
    62  func (env *maasEnviron) createDevice(hostInstanceID instance.Id, hostname string, primaryMACAddress string) (*maasDevice, error) {
    63  	devicesAPI := env.getMAASClient().GetSubObject("devices")
    64  	params := make(url.Values)
    65  	params.Add("hostname", hostname)
    66  	params.Add("parent", extractSystemId(hostInstanceID))
    67  	params.Add("mac_addresses", primaryMACAddress)
    68  
    69  	result, err := devicesAPI.CallPost("new", params)
    70  	if err != nil {
    71  		return nil, errors.Trace(err)
    72  	}
    73  	deviceJSON, err := getJSONBytes(result)
    74  	if err != nil {
    75  		return nil, errors.Trace(err)
    76  	}
    77  	device, err := parseDevice(deviceJSON)
    78  	if err != nil {
    79  		return nil, errors.Trace(err)
    80  	}
    81  	logger.Debugf("created device: %+v", device)
    82  	return device, nil
    83  }
    84  
    85  func parseInterface(jsonBytes []byte) (*maasInterface, error) {
    86  	var iface maasInterface
    87  	if err := json.Unmarshal(jsonBytes, &iface); err != nil {
    88  		return nil, errors.Annotate(err, "parsing interface")
    89  	}
    90  	return &iface, nil
    91  }
    92  
    93  func (env *maasEnviron) createDeviceInterface(deviceID instance.Id, name, macAddress, vlanID string) (*maasInterface, error) {
    94  	deviceSystemID := extractSystemId(deviceID)
    95  	uri := path.Join("nodes", deviceSystemID, "interfaces")
    96  	interfacesAPI := env.getMAASClient().GetSubObject(uri)
    97  
    98  	params := make(url.Values)
    99  	params.Add("name", name)
   100  	params.Add("mac_address", macAddress)
   101  	params.Add("vlan", vlanID)
   102  
   103  	result, err := interfacesAPI.CallPost("create_physical", params)
   104  	if err != nil {
   105  		return nil, errors.Trace(err)
   106  	}
   107  	interfaceJSON, err := getJSONBytes(result)
   108  	if err != nil {
   109  		return nil, errors.Trace(err)
   110  	}
   111  	iface, err := parseInterface(interfaceJSON)
   112  	if err != nil {
   113  		return nil, errors.Trace(err)
   114  	}
   115  	return iface, nil
   116  }
   117  
   118  func (env *maasEnviron) updateDeviceInterface(deviceID instance.Id, interfaceID, name, macAddress, vlanID string) (*maasInterface, error) {
   119  	deviceSystemID := extractSystemId(deviceID)
   120  	uri := path.Join("nodes", deviceSystemID, "interfaces", interfaceID)
   121  	interfacesAPI := env.getMAASClient().GetSubObject(uri)
   122  
   123  	params := make(url.Values)
   124  	params.Add("name", name)
   125  	params.Add("mac_address", macAddress)
   126  	params.Add("vlan", vlanID)
   127  
   128  	result, err := interfacesAPI.Update(params)
   129  	if err != nil {
   130  		return nil, errors.Trace(err)
   131  	}
   132  	interfaceJSON, err := getJSONBytes(result)
   133  	if err != nil {
   134  		return nil, errors.Trace(err)
   135  	}
   136  	iface, err := parseInterface(interfaceJSON)
   137  	if err != nil {
   138  		return nil, errors.Trace(err)
   139  	}
   140  	return iface, nil
   141  }
   142  
   143  func (env *maasEnviron) linkDeviceInterfaceToSubnet(deviceID instance.Id, interfaceID, subnetID string, mode maasLinkMode) (*maasInterface, error) {
   144  	deviceSystemID := extractSystemId(deviceID)
   145  	uri := path.Join("nodes", deviceSystemID, "interfaces", interfaceID)
   146  	interfacesAPI := env.getMAASClient().GetSubObject(uri)
   147  
   148  	params := make(url.Values)
   149  	params.Add("mode", string(mode))
   150  	params.Add("subnet", subnetID)
   151  
   152  	result, err := interfacesAPI.CallPost("link_subnet", params)
   153  	if err != nil {
   154  		return nil, errors.Trace(err)
   155  	}
   156  	interfaceJSON, err := getJSONBytes(result)
   157  	if err != nil {
   158  		return nil, errors.Trace(err)
   159  	}
   160  	iface, err := parseInterface(interfaceJSON)
   161  	if err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  	return iface, nil
   165  }
   166  
   167  func (env *maasEnviron) deviceInterfaces(deviceID instance.Id) ([]maasInterface, error) {
   168  	deviceSystemID := extractSystemId(deviceID)
   169  	uri := path.Join("nodes", deviceSystemID, "interfaces")
   170  	interfacesAPI := env.getMAASClient().GetSubObject(uri)
   171  
   172  	result, err := interfacesAPI.CallGet("", nil)
   173  	if err != nil {
   174  		return nil, errors.Trace(err)
   175  	}
   176  	interfacesJSON, err := getJSONBytes(result)
   177  	if err != nil {
   178  		return nil, errors.Trace(err)
   179  	}
   180  	interfaces, err := parseInterfaces(interfacesJSON)
   181  	if err != nil {
   182  		return nil, errors.Trace(err)
   183  	}
   184  	logger.Debugf("device %q interfaces: %+v", deviceSystemID, interfaces)
   185  	return interfaces, nil
   186  
   187  }
   188  
   189  func (env *maasEnviron) deviceInterfaceInfo(deviceID instance.Id, nameToParentName map[string]string) ([]network.InterfaceInfo, error) {
   190  	interfaces, err := env.deviceInterfaces(deviceID)
   191  	if err != nil {
   192  		return nil, errors.Trace(err)
   193  	}
   194  
   195  	interfaceInfo := make([]network.InterfaceInfo, 0, len(interfaces))
   196  	for _, nic := range interfaces {
   197  		nicInfo := network.InterfaceInfo{
   198  			InterfaceName:       nic.Name,
   199  			InterfaceType:       network.EthernetInterface,
   200  			MACAddress:          nic.MACAddress,
   201  			MTU:                 nic.EffectveMTU,
   202  			VLANTag:             nic.VLAN.VID,
   203  			ProviderId:          network.Id(strconv.Itoa(nic.ID)),
   204  			ProviderVLANId:      network.Id(strconv.Itoa(nic.VLAN.ID)),
   205  			Disabled:            !nic.Enabled,
   206  			NoAutoStart:         !nic.Enabled,
   207  			ParentInterfaceName: nameToParentName[nic.Name],
   208  		}
   209  
   210  		if len(nic.Links) == 0 {
   211  			logger.Debugf("device %q interface %q has no links", deviceID, nic.Name)
   212  			interfaceInfo = append(interfaceInfo, nicInfo)
   213  			continue
   214  		}
   215  
   216  		for _, link := range nic.Links {
   217  			nicInfo.ConfigType = maasLinkToInterfaceConfigType(string(link.Mode))
   218  
   219  			if link.IPAddress == "" {
   220  				logger.Debugf("device %q interface %q has no address", deviceID, nic.Name)
   221  				interfaceInfo = append(interfaceInfo, nicInfo)
   222  				continue
   223  			}
   224  
   225  			if link.Subnet == nil {
   226  				logger.Debugf("device %q interface %q link %d missing subnet", deviceID, nic.Name, link.ID)
   227  				interfaceInfo = append(interfaceInfo, nicInfo)
   228  				continue
   229  			}
   230  
   231  			nicInfo.CIDR = link.Subnet.CIDR
   232  			nicInfo.Address = network.NewAddressOnSpace(link.Subnet.Space, link.IPAddress)
   233  			nicInfo.ProviderSubnetId = network.Id(strconv.Itoa(link.Subnet.ID))
   234  			nicInfo.ProviderAddressId = network.Id(strconv.Itoa(link.ID))
   235  			if link.Subnet.GatewayIP != "" {
   236  				nicInfo.GatewayAddress = network.NewAddressOnSpace(link.Subnet.Space, link.Subnet.GatewayIP)
   237  			}
   238  			if len(link.Subnet.DNSServers) > 0 {
   239  				nicInfo.DNSServers = network.NewAddressesOnSpace(link.Subnet.Space, link.Subnet.DNSServers...)
   240  			}
   241  
   242  			interfaceInfo = append(interfaceInfo, nicInfo)
   243  		}
   244  	}
   245  	logger.Debugf("device %q has interface info: %+v", deviceID, interfaceInfo)
   246  	return interfaceInfo, nil
   247  }
   248  
   249  func (env *maasEnviron) deviceInterfaceInfo2(device gomaasapi.Device, nameToParentName map[string]string, subnetToStaticRoutes map[string][]gomaasapi.StaticRoute) ([]network.InterfaceInfo, error) {
   250  	deviceID := device.SystemID()
   251  	interfaces := device.InterfaceSet()
   252  
   253  	interfaceInfo := make([]network.InterfaceInfo, 0, len(interfaces))
   254  	for idx, nic := range interfaces {
   255  		vlanId := 0
   256  		vlanVid := 0
   257  		vlan := nic.VLAN()
   258  		if vlan != nil {
   259  			vlanId = vlan.ID()
   260  			vlanVid = vlan.VID()
   261  		}
   262  		nicInfo := network.InterfaceInfo{
   263  			DeviceIndex:         idx,
   264  			InterfaceName:       nic.Name(),
   265  			InterfaceType:       network.EthernetInterface,
   266  			MACAddress:          nic.MACAddress(),
   267  			MTU:                 nic.EffectiveMTU(),
   268  			VLANTag:             vlanVid,
   269  			ProviderId:          network.Id(strconv.Itoa(nic.ID())),
   270  			ProviderVLANId:      network.Id(strconv.Itoa(vlanId)),
   271  			Disabled:            !nic.Enabled(),
   272  			NoAutoStart:         !nic.Enabled(),
   273  			ParentInterfaceName: nameToParentName[nic.Name()],
   274  		}
   275  		for _, link := range nic.Links() {
   276  			subnet := link.Subnet()
   277  			if subnet == nil {
   278  				continue
   279  			}
   280  			routes := subnetToStaticRoutes[subnet.CIDR()]
   281  			for _, route := range routes {
   282  				nicInfo.Routes = append(nicInfo.Routes, network.Route{
   283  					DestinationCIDR: route.Destination().CIDR(),
   284  					GatewayIP:       route.GatewayIP(),
   285  					Metric:          route.Metric(),
   286  				})
   287  			}
   288  		}
   289  
   290  		if len(nic.Links()) == 0 {
   291  			logger.Debugf("device %q interface %q has no links", deviceID, nic.Name())
   292  			interfaceInfo = append(interfaceInfo, nicInfo)
   293  			continue
   294  		}
   295  
   296  		for _, link := range nic.Links() {
   297  			nicInfo.ConfigType = maasLinkToInterfaceConfigType(link.Mode())
   298  
   299  			subnet := link.Subnet()
   300  			if link.IPAddress() == "" || subnet == nil {
   301  				logger.Debugf("device %q interface %q has no address", deviceID, nic.Name())
   302  				interfaceInfo = append(interfaceInfo, nicInfo)
   303  				continue
   304  			}
   305  
   306  			nicInfo.CIDR = subnet.CIDR()
   307  			nicInfo.Address = network.NewAddressOnSpace(subnet.Space(), link.IPAddress())
   308  			nicInfo.ProviderSubnetId = network.Id(strconv.Itoa(subnet.ID()))
   309  			nicInfo.ProviderAddressId = network.Id(strconv.Itoa(link.ID()))
   310  			if subnet.Gateway() != "" {
   311  				nicInfo.GatewayAddress = network.NewAddressOnSpace(subnet.Space(), subnet.Gateway())
   312  			}
   313  			if len(subnet.DNSServers()) > 0 {
   314  				nicInfo.DNSServers = network.NewAddressesOnSpace(subnet.Space(), subnet.DNSServers()...)
   315  			}
   316  
   317  			interfaceInfo = append(interfaceInfo, nicInfo)
   318  		}
   319  	}
   320  	logger.Debugf("device %q has interface info: %+v", deviceID, interfaceInfo)
   321  	return interfaceInfo, nil
   322  }
   323  
   324  type deviceCreatorParams struct {
   325  	Name                 string
   326  	Subnet               gomaasapi.Subnet // may be nil
   327  	PrimaryMACAddress    string
   328  	PrimaryNICName       string
   329  	DesiredInterfaceInfo []network.InterfaceInfo
   330  	CIDRToMAASSubnet     map[string]gomaasapi.Subnet
   331  	CIDRToStaticRoutes   map[string][]gomaasapi.StaticRoute
   332  	Machine              gomaasapi.Machine
   333  }
   334  
   335  func (env *maasEnviron) createAndPopulateDevice(params deviceCreatorParams) (gomaasapi.Device, error) {
   336  	createDeviceArgs := gomaasapi.CreateMachineDeviceArgs{
   337  		Hostname:      params.Name,
   338  		MACAddress:    params.PrimaryMACAddress,
   339  		Subnet:        params.Subnet, // can be nil
   340  		InterfaceName: params.PrimaryNICName,
   341  	}
   342  	device, err := params.Machine.CreateDevice(createDeviceArgs)
   343  	if err != nil {
   344  		return nil, errors.Trace(err)
   345  	}
   346  	defer func() {
   347  		if err != nil {
   348  			device.Delete()
   349  		}
   350  	}()
   351  	interface_set := device.InterfaceSet()
   352  	if len(interface_set) != 1 {
   353  		// Shouldn't be possible as machine.CreateDevice always
   354  		// returns a device with one interface.
   355  		names := make([]string, len(interface_set))
   356  		for i, iface := range interface_set {
   357  			names[i] = iface.Name()
   358  		}
   359  		err = errors.Errorf("unexpected number of interfaces "+
   360  			"in response from creating device: %v", names)
   361  		return nil, err
   362  	}
   363  	primaryNIC := interface_set[0]
   364  	primaryNICVLAN := primaryNIC.VLAN()
   365  
   366  	interfaceCreated := false
   367  	// Populate the rest of the desired interfaces on this device
   368  	for _, nic := range params.DesiredInterfaceInfo {
   369  		if nic.InterfaceName == params.PrimaryNICName {
   370  			// already handled in CreateDevice
   371  			continue
   372  		}
   373  		// We have to register an extra interface for this container
   374  		// (aka 'device'), and then link that device to the desired
   375  		// subnet so that it can acquire an IP address from MAAS.
   376  		createArgs := gomaasapi.CreateInterfaceArgs{
   377  			Name:       nic.InterfaceName,
   378  			MTU:        nic.MTU,
   379  			MACAddress: nic.MACAddress,
   380  		}
   381  
   382  		subnet, knownSubnet := params.CIDRToMAASSubnet[nic.CIDR]
   383  		if !knownSubnet {
   384  			logger.Warningf("NIC %v has no subnet - setting to manual and using 'primaryNIC' VLAN %d", nic.InterfaceName, primaryNICVLAN.ID())
   385  			createArgs.VLAN = primaryNICVLAN
   386  		} else {
   387  			createArgs.VLAN = subnet.VLAN()
   388  			logger.Infof("linking NIC %v to subnet %v - using static IP", nic.InterfaceName, subnet.CIDR())
   389  		}
   390  
   391  		createdNIC, err := device.CreateInterface(createArgs)
   392  		if err != nil {
   393  			return nil, errors.Annotate(err, "creating device interface")
   394  		}
   395  		logger.Debugf("created device interface: %+v", createdNIC)
   396  		interfaceCreated = true
   397  
   398  		if !knownSubnet {
   399  			// If we didn't request an explicit subnet, then we
   400  			// don't need to link the device to that subnet
   401  			continue
   402  		}
   403  
   404  		linkArgs := gomaasapi.LinkSubnetArgs{
   405  			Mode:   gomaasapi.LinkModeStatic,
   406  			Subnet: subnet,
   407  		}
   408  
   409  		if err := createdNIC.LinkSubnet(linkArgs); err != nil {
   410  			return nil, errors.Annotatef(err, "linking NIC %v to subnet %v", nic.InterfaceName, subnet.CIDR())
   411  		} else {
   412  			logger.Debugf("linked device interface to subnet: %+v", createdNIC)
   413  		}
   414  	}
   415  	// If we have created any secondary interfaces we need to reload device from maas
   416  	// so that the changes are reflected in structure.
   417  	if interfaceCreated {
   418  		deviceID := device.SystemID()
   419  		args := gomaasapi.DevicesArgs{SystemIDs: []string{deviceID}}
   420  		devices, err := env.maasController.Devices(args)
   421  		if err != nil {
   422  			return nil, errors.Trace(err)
   423  		}
   424  		if len(devices) != 1 {
   425  			err = errors.Errorf("unexpected response requesting device %v: %v", deviceID, devices)
   426  			return nil, err
   427  		}
   428  		device = devices[0]
   429  	}
   430  	return device, nil
   431  }
   432  
   433  func (env *maasEnviron) lookupSubnets() (map[string]gomaasapi.Subnet, error) {
   434  	subnetCIDRToSubnet := make(map[string]gomaasapi.Subnet)
   435  	spaces, err := env.maasController.Spaces()
   436  	if err != nil {
   437  		return nil, errors.Trace(err)
   438  	}
   439  	for _, space := range spaces {
   440  		for _, subnet := range space.Subnets() {
   441  			subnetCIDRToSubnet[subnet.CIDR()] = subnet
   442  		}
   443  	}
   444  	return subnetCIDRToSubnet, nil
   445  }
   446  func (env *maasEnviron) lookupStaticRoutes() (map[string][]gomaasapi.StaticRoute, error) {
   447  	// map from the source subnet (what subnet is the device in), to what
   448  	// static routes should be used.
   449  	subnetToStaticRoutes := make(map[string][]gomaasapi.StaticRoute)
   450  	staticRoutes, err := env.maasController.StaticRoutes()
   451  	if err != nil {
   452  		// MAAS 2.0 does not support static-routes, and will return a 404. MAAS
   453  		// does not report support for static-routes in its capabilities, nor
   454  		// does it have a different API version between 2.1 and 2.0. So we make
   455  		// the attempt, and treat a 404 as not having any configured static
   456  		// routes.
   457  		// gomaaasapi wraps a ServerError in an UnexpectedError, so we need to
   458  		// dig to make sure we have the right cause:
   459  		handled := false
   460  		if gomaasapi.IsUnexpectedError(err) {
   461  			msg := err.Error()
   462  			if strings.Contains(msg, "404") &&
   463  				strings.Contains(msg, "Unknown API endpoint:") &&
   464  				strings.Contains(msg, "/static-routes/") {
   465  				logger.Debugf("static-routes not supported: %v", err)
   466  				handled = true
   467  				staticRoutes = nil
   468  			} else {
   469  				logger.Warningf("looking up static routes generated IsUnexpectedError, but didn't match: %q %#v", msg, err)
   470  			}
   471  		} else {
   472  			logger.Warningf("not IsUnexpectedError: %#v", err)
   473  		}
   474  		if !handled {
   475  			logger.Warningf("error looking up static-routes: %v", err)
   476  			return nil, errors.Annotate(err, "unable to look up static-routes")
   477  		}
   478  	}
   479  	for _, route := range staticRoutes {
   480  		source := route.Source()
   481  		sourceCIDR := source.CIDR()
   482  		subnetToStaticRoutes[sourceCIDR] = append(subnetToStaticRoutes[sourceCIDR], route)
   483  	}
   484  	logger.Debugf("found static routes: %# v", subnetToStaticRoutes)
   485  	return subnetToStaticRoutes, nil
   486  }
   487  
   488  func (env *maasEnviron) prepareDeviceDetails(name string, machine gomaasapi.Machine, preparedInfo []network.InterfaceInfo) (deviceCreatorParams, error) {
   489  	var zeroParams deviceCreatorParams
   490  
   491  	subnetCIDRToSubnet, err := env.lookupSubnets()
   492  	if err != nil {
   493  		return zeroParams, errors.Trace(err)
   494  	}
   495  	subnetToStaticRoutes, err := env.lookupStaticRoutes()
   496  	if err != nil {
   497  		return zeroParams, errors.Trace(err)
   498  	}
   499  	params := deviceCreatorParams{
   500  		// Containers always use 'eth0' as their primary NIC
   501  		// XXX(jam) 2017-04-13: Except we *don't* do that for KVM containers running Xenial
   502  		Name:                 name,
   503  		Machine:              machine,
   504  		PrimaryNICName:       "eth0",
   505  		DesiredInterfaceInfo: preparedInfo,
   506  		CIDRToMAASSubnet:     subnetCIDRToSubnet,
   507  		CIDRToStaticRoutes:   subnetToStaticRoutes,
   508  	}
   509  
   510  	var primaryNICInfo network.InterfaceInfo
   511  	for _, nic := range preparedInfo {
   512  		if nic.InterfaceName == params.PrimaryNICName {
   513  			primaryNICInfo = nic
   514  			break
   515  		}
   516  	}
   517  	if primaryNICInfo.InterfaceName == "" {
   518  		return zeroParams, errors.Errorf("cannot find primary interface for container")
   519  	}
   520  	logger.Debugf("primary device NIC prepared info: %+v", primaryNICInfo)
   521  
   522  	primaryNICSubnetCIDR := primaryNICInfo.CIDR
   523  	subnet, hasSubnet := subnetCIDRToSubnet[primaryNICSubnetCIDR]
   524  	if hasSubnet {
   525  		params.Subnet = subnet
   526  	} else {
   527  		logger.Debugf("primary device NIC %q has no linked subnet - leaving unconfigured", primaryNICInfo.InterfaceName)
   528  	}
   529  	params.PrimaryMACAddress = primaryNICInfo.MACAddress
   530  	return params, nil
   531  }
   532  
   533  func validateExistingDevice(netInfo []network.InterfaceInfo, device gomaasapi.Device) (bool, error) {
   534  	// Compare the desired device characteristics with the actual device
   535  	interfaces := device.InterfaceSet()
   536  	if len(interfaces) < len(netInfo) {
   537  		logger.Debugf("existing device doesn't have enough interfaces, wanted %d, found %d", len(netInfo), len(interfaces))
   538  		return false, nil
   539  	}
   540  	actualByMAC := make(map[string]gomaasapi.Interface, len(interfaces))
   541  	for _, iface := range interfaces {
   542  		actualByMAC[iface.MACAddress()] = iface
   543  	}
   544  	for _, desired := range netInfo {
   545  		actual, ok := actualByMAC[desired.MACAddress]
   546  		if !ok {
   547  			foundMACs := make([]string, 0, len(actualByMAC))
   548  			for _, iface := range interfaces {
   549  				foundMACs = append(foundMACs, fmt.Sprintf("%s: %s", iface.Name(), iface.MACAddress()))
   550  			}
   551  			found := strings.Join(foundMACs, ", ")
   552  			logger.Debugf("existing device doesn't have device for MAC Address %q, found: %s", desired.MACAddress, found)
   553  			// No such network interface
   554  			return false, nil
   555  		}
   556  		// TODO: we should have a way to know what space we are targeting, rather than a desired subnet CIDR
   557  		foundCIDR := false
   558  		for _, link := range actual.Links() {
   559  			subnet := link.Subnet()
   560  			if subnet != nil {
   561  				cidr := subnet.CIDR()
   562  				if cidr == desired.CIDR {
   563  					foundCIDR = true
   564  				}
   565  			}
   566  		}
   567  		if !foundCIDR {
   568  			logger.Debugf("could not find Subnet link for CIDR: %q", desired.CIDR)
   569  			return false, nil
   570  		}
   571  	}
   572  	return true, nil
   573  }
   574  
   575  // checkForExistingDevice checks to see if we've already registered a device
   576  // with this name, and if its information is appropriately populated. If we
   577  // have, then we just return the existing interface info. If we find it, but
   578  // it doesn't match, then we ask MAAS to remove it, which should cause the
   579  // calling code to create it again.
   580  func (env *maasEnviron) checkForExistingDevice(params deviceCreatorParams) (gomaasapi.Device, error) {
   581  	devicesArgs := gomaasapi.DevicesArgs{
   582  		Hostname: []string{params.Name},
   583  	}
   584  	maybeDevices, err := params.Machine.Devices(devicesArgs)
   585  	if err != nil {
   586  		logger.Warningf("error while trying to lookup %q: %v", params.Name, err)
   587  		// not considered fatal, since we'll attempt to create the device if we didn't find it
   588  		return nil, nil
   589  	}
   590  	if len(maybeDevices) == 0 {
   591  		logger.Debugf("no existing MAAS devices for container %q, creating", params.Name)
   592  		return nil, nil
   593  	}
   594  	if len(maybeDevices) > 1 {
   595  		logger.Warningf("found more than 1 MAAS devices (%d) for container %q", len(maybeDevices),
   596  			params.Name)
   597  		return nil, errors.Errorf("found more than 1 MAAS device (%d) for container %q",
   598  			len(maybeDevices), params.Name)
   599  	}
   600  	device := maybeDevices[0]
   601  	// Now validate that this device has the right interfaces
   602  	matches, err := validateExistingDevice(params.DesiredInterfaceInfo, device)
   603  	if err != nil {
   604  		return nil, err
   605  	}
   606  	if matches {
   607  		logger.Debugf("found MAAS device for container %q using existing device", params.Name)
   608  		return device, nil
   609  	}
   610  	logger.Debugf("found existing MAAS device for container %q but interfaces did not match, removing device", params.Name)
   611  	// We found a device, but it doesn't match what we need. remove it and we'll create again.
   612  	device.Delete()
   613  	return nil, nil
   614  }