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