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

     1  // Copyright 2015 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  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/gomaasapi"
    13  
    14  	"github.com/juju/juju/core/instance"
    15  	"github.com/juju/juju/environs/context"
    16  	"github.com/juju/juju/network"
    17  )
    18  
    19  ////////////////////////////////////////////////////////////////////////////////
    20  // TODO(dimitern): The types below should be part of gomaasapi.
    21  // LKK Card: https://canonical.leankit.com/Boards/View/101652562/119310616
    22  
    23  type maasLinkMode string
    24  
    25  const (
    26  	modeUnknown maasLinkMode = ""
    27  	modeStatic  maasLinkMode = "static"
    28  	modeDHCP    maasLinkMode = "dhcp"
    29  	modeLinkUp  maasLinkMode = "link_up"
    30  	modeAuto    maasLinkMode = "auto"
    31  )
    32  
    33  type maasInterfaceLink struct {
    34  	ID        int          `json:"id"`
    35  	Subnet    *maasSubnet  `json:"subnet,omitempty"`
    36  	IPAddress string       `json:"ip_address,omitempty"`
    37  	Mode      maasLinkMode `json:"mode"`
    38  }
    39  
    40  type maasInterfaceType string
    41  
    42  const (
    43  	typeUnknown  maasInterfaceType = ""
    44  	typePhysical maasInterfaceType = "physical"
    45  	typeVLAN     maasInterfaceType = "vlan"
    46  	typeBond     maasInterfaceType = "bond"
    47  	typeBridge   maasInterfaceType = "bridge"
    48  )
    49  
    50  type maasInterface struct {
    51  	ID      int               `json:"id"`
    52  	Name    string            `json:"name"`
    53  	Type    maasInterfaceType `json:"type"`
    54  	Enabled bool              `json:"enabled"`
    55  
    56  	MACAddress  string   `json:"mac_address"`
    57  	VLAN        maasVLAN `json:"vlan"`
    58  	EffectveMTU int      `json:"effective_mtu"`
    59  
    60  	Links []maasInterfaceLink `json:"links"`
    61  
    62  	Parents  []string `json:"parents"`
    63  	Children []string `json:"children"`
    64  
    65  	ResourceURI string `json:"resource_uri"`
    66  }
    67  
    68  type maasVLAN struct {
    69  	ID          int    `json:"id"`
    70  	Name        string `json:"name"`
    71  	VID         int    `json:"vid"`
    72  	MTU         int    `json:"mtu"`
    73  	Fabric      string `json:"fabric"`
    74  	ResourceURI string `json:"resource_uri"`
    75  }
    76  
    77  type maasSubnet struct {
    78  	ID          int      `json:"id"`
    79  	Name        string   `json:"name"`
    80  	Space       string   `json:"space"`
    81  	VLAN        maasVLAN `json:"vlan"`
    82  	GatewayIP   string   `json:"gateway_ip"`
    83  	DNSServers  []string `json:"dns_servers"`
    84  	CIDR        string   `json:"cidr"`
    85  	ResourceURI string   `json:"resource_uri"`
    86  }
    87  
    88  // NetworkInterfaces implements Environ.NetworkInterfaces.
    89  func (env *maasEnviron) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) {
    90  	inst, err := env.getInstance(ctx, instId)
    91  	if err != nil {
    92  		return nil, errors.Trace(err)
    93  	}
    94  	subnetsMap, err := env.subnetToSpaceIds(ctx)
    95  	if err != nil {
    96  		return nil, errors.Trace(err)
    97  	}
    98  	if env.usingMAAS2() {
    99  		dnsSearchDomains, err := env.Domains(ctx)
   100  		if err != nil {
   101  			return nil, errors.Trace(err)
   102  		}
   103  		return maas2NetworkInterfaces(ctx, inst.(*maas2Instance), subnetsMap, dnsSearchDomains...)
   104  	} else {
   105  		mi := inst.(*maas1Instance)
   106  		return maasObjectNetworkInterfaces(ctx, mi.maasObject, subnetsMap)
   107  	}
   108  }
   109  
   110  // maasObjectNetworkInterfaces implements environs.NetworkInterfaces() using the
   111  // new (1.9+) MAAS API, parsing the node details JSON embedded into the given
   112  // maasObject to extract all the relevant InterfaceInfo fields. It returns an
   113  // error satisfying errors.IsNotSupported() if it cannot find the required
   114  // "interface_set" node details field.
   115  func maasObjectNetworkInterfaces(ctx context.ProviderCallContext, maasObject *gomaasapi.MAASObject, subnetsMap map[string]network.Id) ([]network.InterfaceInfo, error) {
   116  	interfaceSet, ok := maasObject.GetMap()["interface_set"]
   117  	if !ok || interfaceSet.IsNil() {
   118  		// This means we're using an older MAAS API.
   119  		return nil, errors.NotSupportedf("interface_set")
   120  	}
   121  
   122  	// TODO(dimitern): Change gomaasapi JSONObject to give access to the raw
   123  	// JSON bytes directly, rather than having to do call MarshalJSON just so
   124  	// the result can be unmarshaled from it.
   125  	//
   126  	// LKK Card: https://canonical.leankit.com/Boards/View/101652562/119311323
   127  
   128  	rawBytes, err := interfaceSet.MarshalJSON()
   129  	if err != nil {
   130  		return nil, errors.Annotate(err, "cannot get interface_set JSON bytes")
   131  	}
   132  
   133  	interfaces, err := parseInterfaces(rawBytes)
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  
   138  	infos := make([]network.InterfaceInfo, 0, len(interfaces))
   139  	for i, iface := range interfaces {
   140  		// The below works for all types except bonds and their members.
   141  		parentName := strings.Join(iface.Parents, "")
   142  		var nicType network.InterfaceType
   143  		switch iface.Type {
   144  		case typePhysical:
   145  			nicType = network.EthernetInterface
   146  			children := strings.Join(iface.Children, "")
   147  			if parentName == "" && len(iface.Children) == 1 && strings.HasPrefix(children, "bond") {
   148  				// FIXME: Verify the bond exists, regardless of its name.
   149  				// This is a bond member, set the parent correctly (from
   150  				// Juju's perspective) - to the bond itself.
   151  				parentName = children
   152  			}
   153  		case typeBond:
   154  			parentName = ""
   155  			nicType = network.BondInterface
   156  		case typeVLAN:
   157  			nicType = network.VLAN_8021QInterface
   158  		case typeBridge:
   159  			nicType = network.BridgeInterface
   160  		}
   161  
   162  		nicInfo := network.InterfaceInfo{
   163  			DeviceIndex:         i,
   164  			MACAddress:          iface.MACAddress,
   165  			ProviderId:          network.Id(fmt.Sprintf("%v", iface.ID)),
   166  			VLANTag:             iface.VLAN.VID,
   167  			InterfaceName:       iface.Name,
   168  			InterfaceType:       nicType,
   169  			ParentInterfaceName: parentName,
   170  			Disabled:            !iface.Enabled,
   171  			NoAutoStart:         !iface.Enabled,
   172  		}
   173  
   174  		if len(iface.Links) == 0 {
   175  			logger.Debugf("interface %q has no links", iface.Name)
   176  			infos = append(infos, nicInfo)
   177  			continue
   178  		}
   179  
   180  		for _, link := range iface.Links {
   181  			nicInfo.ConfigType = maasLinkToInterfaceConfigType(string(link.Mode))
   182  
   183  			if link.IPAddress == "" && link.Subnet == nil {
   184  				logger.Debugf("interface %q link %d has neither subnet nor address", iface.Name, link.ID)
   185  				infos = append(infos, nicInfo)
   186  			} else {
   187  				// We set it here initially without a space, just so we don't
   188  				// lose it when we have no linked subnet below.
   189  				nicInfo.Address = network.NewAddress(link.IPAddress)
   190  				nicInfo.ProviderAddressId = network.Id(fmt.Sprintf("%v", link.ID))
   191  			}
   192  
   193  			sub := link.Subnet
   194  			if sub == nil {
   195  				logger.Debugf("interface %q link %d missing subnet", iface.Name, link.ID)
   196  				infos = append(infos, nicInfo)
   197  				continue
   198  			}
   199  
   200  			nicInfo.CIDR = sub.CIDR
   201  			nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID))
   202  			nicInfo.ProviderVLANId = network.Id(fmt.Sprintf("%v", sub.VLAN.ID))
   203  
   204  			// Now we know the subnet and space, we can update the address to
   205  			// store the space with it.
   206  			nicInfo.Address = network.NewAddressOnSpace(sub.Space, link.IPAddress)
   207  			spaceId, ok := subnetsMap[sub.CIDR]
   208  			if !ok {
   209  				// The space we found is not recognised.
   210  				// No provider space info is available.
   211  				logger.Warningf("interface %q link %d has unrecognised space %q", iface.Name, link.ID, sub.Space)
   212  			} else {
   213  				nicInfo.Address.SpaceProviderId = spaceId
   214  				nicInfo.ProviderSpaceId = spaceId
   215  			}
   216  
   217  			gwAddr := network.NewAddressOnSpace(sub.Space, sub.GatewayIP)
   218  			nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space, sub.DNSServers...)
   219  			if ok {
   220  				gwAddr.SpaceProviderId = spaceId
   221  				for i := range nicInfo.DNSServers {
   222  					nicInfo.DNSServers[i].SpaceProviderId = spaceId
   223  				}
   224  			}
   225  			nicInfo.GatewayAddress = gwAddr
   226  			nicInfo.MTU = sub.VLAN.MTU
   227  
   228  			// Each link we represent as a separate InterfaceInfo, but with the
   229  			// same name and device index, just different address, subnet, etc.
   230  			infos = append(infos, nicInfo)
   231  		}
   232  	}
   233  	return infos, nil
   234  }
   235  
   236  func maas2NetworkInterfaces(ctx context.ProviderCallContext, instance *maas2Instance, subnetsMap map[string]network.Id, dnsSearchDomains ...string) ([]network.InterfaceInfo, error) {
   237  	interfaces := instance.machine.InterfaceSet()
   238  	infos := make([]network.InterfaceInfo, 0, len(interfaces))
   239  	for i, iface := range interfaces {
   240  
   241  		// The below works for all types except bonds and their members.
   242  		parentName := strings.Join(iface.Parents(), "")
   243  		var nicType network.InterfaceType
   244  		switch maasInterfaceType(iface.Type()) {
   245  		case typePhysical:
   246  			nicType = network.EthernetInterface
   247  			children := strings.Join(iface.Children(), "")
   248  			if parentName == "" && len(iface.Children()) == 1 && strings.HasPrefix(children, "bond") {
   249  				// FIXME: Verify the bond exists, regardless of its name.
   250  				// This is a bond member, set the parent correctly (from
   251  				// Juju's perspective) - to the bond itself.
   252  				parentName = children
   253  			}
   254  		case typeBond:
   255  			parentName = ""
   256  			nicType = network.BondInterface
   257  		case typeVLAN:
   258  			nicType = network.VLAN_8021QInterface
   259  		case typeBridge:
   260  			nicType = network.BridgeInterface
   261  		}
   262  
   263  		vlanTag := 0
   264  		if iface.VLAN() != nil {
   265  			vlanTag = iface.VLAN().VID()
   266  		}
   267  		nicInfo := network.InterfaceInfo{
   268  			DeviceIndex:         i,
   269  			MACAddress:          iface.MACAddress(),
   270  			ProviderId:          network.Id(fmt.Sprintf("%v", iface.ID())),
   271  			VLANTag:             vlanTag,
   272  			InterfaceName:       iface.Name(),
   273  			InterfaceType:       nicType,
   274  			ParentInterfaceName: parentName,
   275  			Disabled:            !iface.Enabled(),
   276  			NoAutoStart:         !iface.Enabled(),
   277  		}
   278  
   279  		if len(iface.Links()) == 0 {
   280  			logger.Debugf("interface %q has no links", iface.Name())
   281  			infos = append(infos, nicInfo)
   282  			continue
   283  		}
   284  
   285  		for _, link := range iface.Links() {
   286  			nicInfo.ConfigType = maasLinkToInterfaceConfigType(link.Mode())
   287  
   288  			if link.IPAddress() == "" && link.Subnet() == nil {
   289  				logger.Debugf("interface %q link %d has neither subnet nor address", iface.Name(), link.ID())
   290  				infos = append(infos, nicInfo)
   291  			} else {
   292  				// We set it here initially without a space, just so we don't
   293  				// lose it when we have no linked subnet below.
   294  				nicInfo.Address = network.NewAddress(link.IPAddress())
   295  				nicInfo.ProviderAddressId = network.Id(fmt.Sprintf("%v", link.ID()))
   296  			}
   297  
   298  			sub := link.Subnet()
   299  			if sub == nil {
   300  				logger.Debugf("interface %q link %d missing subnet", iface.Name(), link.ID())
   301  				infos = append(infos, nicInfo)
   302  				continue
   303  			}
   304  
   305  			nicInfo.CIDR = sub.CIDR()
   306  			nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID()))
   307  			nicInfo.ProviderVLANId = network.Id(fmt.Sprintf("%v", sub.VLAN().ID()))
   308  
   309  			// Now we know the subnet and space, we can update the address to
   310  			// store the space with it.
   311  			nicInfo.Address = network.NewAddressOnSpace(sub.Space(), link.IPAddress())
   312  			spaceId, ok := subnetsMap[sub.CIDR()]
   313  			if !ok {
   314  				// The space we found is not recognised.
   315  				// No provider space info is available.
   316  				logger.Warningf("interface %q link %d has unrecognised space %q", iface.Name(), link.ID(), sub.Space())
   317  			} else {
   318  				nicInfo.Address.SpaceProviderId = spaceId
   319  				nicInfo.ProviderSpaceId = spaceId
   320  			}
   321  
   322  			gwAddr := network.NewAddressOnSpace(sub.Space(), sub.Gateway())
   323  			nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space(), sub.DNSServers()...)
   324  			if ok {
   325  				gwAddr.SpaceProviderId = spaceId
   326  				for i := range nicInfo.DNSServers {
   327  					nicInfo.DNSServers[i].SpaceProviderId = spaceId
   328  				}
   329  			}
   330  			nicInfo.DNSSearchDomains = dnsSearchDomains
   331  			nicInfo.GatewayAddress = gwAddr
   332  			nicInfo.MTU = sub.VLAN().MTU()
   333  
   334  			// Each link we represent as a separate InterfaceInfo, but with the
   335  			// same name and device index, just different address, subnet, etc.
   336  			infos = append(infos, nicInfo)
   337  		}
   338  	}
   339  	return infos, nil
   340  }
   341  
   342  func parseInterfaces(jsonBytes []byte) ([]maasInterface, error) {
   343  	var interfaces []maasInterface
   344  	if err := json.Unmarshal(jsonBytes, &interfaces); err != nil {
   345  		return nil, errors.Annotate(err, "parsing interfaces")
   346  	}
   347  	return interfaces, nil
   348  }
   349  
   350  func maasLinkToInterfaceConfigType(mode string) network.InterfaceConfigType {
   351  	switch maasLinkMode(mode) {
   352  	case modeUnknown:
   353  		return network.ConfigUnknown
   354  	case modeDHCP:
   355  		return network.ConfigDHCP
   356  	case modeStatic, modeAuto:
   357  		return network.ConfigStatic
   358  	case modeLinkUp:
   359  	default:
   360  	}
   361  
   362  	return network.ConfigManual
   363  }