github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  )
    30  
    31  type maasInterfaceLink struct {
    32  	ID        int          `json:"id"`
    33  	Subnet    *maasSubnet  `json:"subnet,omitempty"`
    34  	IPAddress string       `json:"ip_address,omitempty"`
    35  	Mode      maasLinkMode `json:"mode"`
    36  }
    37  
    38  type maasInterfaceType string
    39  
    40  const (
    41  	typeUnknown  maasInterfaceType = ""
    42  	typePhysical maasInterfaceType = "physical"
    43  	typeVLAN     maasInterfaceType = "vlan"
    44  	typeBond     maasInterfaceType = "bond"
    45  )
    46  
    47  type maasInterface struct {
    48  	ID      int               `json:"id"`
    49  	Name    string            `json:"name"`
    50  	Type    maasInterfaceType `json:"type"`
    51  	Enabled bool              `json:"enabled"`
    52  
    53  	MACAddress  string   `json:"mac_address"`
    54  	VLAN        maasVLAN `json:"vlan"`
    55  	EffectveMTU int      `json:"effective_mtu"`
    56  
    57  	Links []maasInterfaceLink `json:"links"`
    58  
    59  	Parents  []string `json:"parents"`
    60  	Children []string `json:"children"`
    61  
    62  	ResourceURI string `json:"resource_uri"`
    63  }
    64  
    65  type maasVLAN struct {
    66  	ID          int    `json:"id"`
    67  	Name        string `json:"name"`
    68  	VID         int    `json:"vid"`
    69  	MTU         int    `json:"mtu"`
    70  	Fabric      string `json:"fabric"`
    71  	ResourceURI string `json:"resource_uri"`
    72  }
    73  
    74  type maasSubnet struct {
    75  	ID          int      `json:"id"`
    76  	Name        string   `json:"name"`
    77  	Space       string   `json:"space"`
    78  	VLAN        maasVLAN `json:"vlan"`
    79  	GatewayIP   string   `json:"gateway_ip"`
    80  	DNSServers  []string `json:"dns_servers"`
    81  	CIDR        string   `json:"cidr"`
    82  	ResourceURI string   `json:"resource_uri"`
    83  }
    84  
    85  func parseInterfaces(jsonBytes []byte) ([]maasInterface, error) {
    86  	var interfaces []maasInterface
    87  	if err := json.Unmarshal(jsonBytes, &interfaces); err != nil {
    88  		return nil, errors.Annotate(err, "parsing interfaces")
    89  	}
    90  	return interfaces, nil
    91  }
    92  
    93  // maasObjectNetworkInterfaces implements environs.NetworkInterfaces() using the
    94  // new (1.9+) MAAS API, parsing the node details JSON embedded into the given
    95  // maasObject to extract all the relevant InterfaceInfo fields. It returns an
    96  // error satisfying errors.IsNotSupported() if it cannot find the required
    97  // "interface_set" node details field.
    98  func maasObjectNetworkInterfaces(maasObject *gomaasapi.MAASObject, subnetsMap map[string]network.Id) ([]network.InterfaceInfo, error) {
    99  	interfaceSet, ok := maasObject.GetMap()["interface_set"]
   100  	if !ok || interfaceSet.IsNil() {
   101  		// This means we're using an older MAAS API.
   102  		return nil, errors.NotSupportedf("interface_set")
   103  	}
   104  
   105  	// TODO(dimitern): Change gomaasapi JSONObject to give access to the raw
   106  	// JSON bytes directly, rather than having to do call MarshalJSON just so
   107  	// the result can be unmarshaled from it.
   108  	//
   109  	// LKK Card: https://canonical.leankit.com/Boards/View/101652562/119311323
   110  
   111  	rawBytes, err := interfaceSet.MarshalJSON()
   112  	if err != nil {
   113  		return nil, errors.Annotate(err, "cannot get interface_set JSON bytes")
   114  	}
   115  
   116  	interfaces, err := parseInterfaces(rawBytes)
   117  	if err != nil {
   118  		return nil, errors.Trace(err)
   119  	}
   120  
   121  	infos := make([]network.InterfaceInfo, 0, len(interfaces))
   122  	for i, iface := range interfaces {
   123  
   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  		for _, link := range iface.Links {
   157  			switch link.Mode {
   158  			case modeUnknown:
   159  				nicInfo.ConfigType = network.ConfigUnknown
   160  			case modeDHCP:
   161  				nicInfo.ConfigType = network.ConfigDHCP
   162  			case modeStatic, modeLinkUp:
   163  				nicInfo.ConfigType = network.ConfigStatic
   164  			default:
   165  				nicInfo.ConfigType = network.ConfigManual
   166  			}
   167  
   168  			if link.IPAddress == "" {
   169  				logger.Debugf("interface %q has no address", iface.Name)
   170  			} else {
   171  				// We set it here initially without a space, just so we don't
   172  				// lose it when we have no linked subnet below.
   173  				nicInfo.Address = network.NewAddress(link.IPAddress)
   174  				nicInfo.ProviderAddressId = network.Id(fmt.Sprintf("%v", link.ID))
   175  			}
   176  
   177  			if link.Subnet == nil {
   178  				logger.Debugf("interface %q link %d missing subnet", iface.Name, link.ID)
   179  				infos = append(infos, nicInfo)
   180  				continue
   181  			}
   182  
   183  			sub := link.Subnet
   184  			nicInfo.CIDR = sub.CIDR
   185  			nicInfo.ProviderSubnetId = network.Id(fmt.Sprintf("%v", sub.ID))
   186  			nicInfo.ProviderVLANId = network.Id(fmt.Sprintf("%v", sub.VLAN.ID))
   187  
   188  			// Now we know the subnet and space, we can update the address to
   189  			// store the space with it.
   190  			nicInfo.Address = network.NewAddressOnSpace(sub.Space, link.IPAddress)
   191  			spaceId, ok := subnetsMap[string(sub.CIDR)]
   192  			if !ok {
   193  				// The space we found is not recognised, no
   194  				// provider id available.
   195  				logger.Warningf("interface %q link %d has unrecognised space %q", iface.Name, link.ID, sub.Space)
   196  			} else {
   197  				nicInfo.Address.SpaceProviderId = spaceId
   198  				nicInfo.ProviderSpaceId = spaceId
   199  			}
   200  
   201  			gwAddr := network.NewAddressOnSpace(sub.Space, sub.GatewayIP)
   202  			nicInfo.DNSServers = network.NewAddressesOnSpace(sub.Space, sub.DNSServers...)
   203  			if ok {
   204  				gwAddr.SpaceProviderId = spaceId
   205  				for i := range nicInfo.DNSServers {
   206  					nicInfo.DNSServers[i].SpaceProviderId = spaceId
   207  				}
   208  			}
   209  			nicInfo.GatewayAddress = gwAddr
   210  			nicInfo.MTU = sub.VLAN.MTU
   211  
   212  			// Each link we represent as a separate InterfaceInfo, but with the
   213  			// same name and device index, just different addres, subnet, etc.
   214  			infos = append(infos, nicInfo)
   215  		}
   216  	}
   217  	return infos, nil
   218  }
   219  
   220  func maas2NetworkInterfaces(instance *maas2Instance, subnetsMap map[string]network.Id) ([]network.InterfaceInfo, error) {
   221  	interfaces := instance.machine.InterfaceSet()
   222  	infos := make([]network.InterfaceInfo, 0, len(interfaces))
   223  	for i, iface := range interfaces {
   224  
   225  		// The below works for all types except bonds and their members.
   226  		parentName := strings.Join(iface.Parents(), "")
   227  		var nicType network.InterfaceType
   228  		switch maasInterfaceType(iface.Type()) {
   229  		case typePhysical:
   230  			nicType = network.EthernetInterface
   231  			children := strings.Join(iface.Children(), "")
   232  			if parentName == "" && len(iface.Children()) == 1 && strings.HasPrefix(children, "bond") {
   233  				// FIXME: Verify the bond exists, regardless of its name.
   234  				// This is a bond member, set the parent correctly (from
   235  				// Juju's perspective) - to the bond itself.
   236  				parentName = children
   237  			}
   238  		case typeBond:
   239  			parentName = ""
   240  			nicType = network.BondInterface
   241  		case typeVLAN:
   242  			nicType = network.VLAN_8021QInterface
   243  		}
   244  
   245  		nicInfo := network.InterfaceInfo{
   246  			DeviceIndex:         i,
   247  			MACAddress:          iface.MACAddress(),
   248  			ProviderId:          network.Id(fmt.Sprintf("%v", iface.ID())),
   249  			VLANTag:             iface.VLAN().VID(),
   250  			InterfaceName:       iface.Name(),
   251  			InterfaceType:       nicType,
   252  			ParentInterfaceName: parentName,
   253  			Disabled:            !iface.Enabled(),
   254  			NoAutoStart:         !iface.Enabled(),
   255  		}
   256  
   257  		for _, link := range iface.Links() {
   258  			switch maasLinkMode(link.Mode()) {
   259  			case modeUnknown:
   260  				nicInfo.ConfigType = network.ConfigUnknown
   261  			case modeDHCP:
   262  				nicInfo.ConfigType = network.ConfigDHCP
   263  			case modeStatic, modeLinkUp:
   264  				nicInfo.ConfigType = network.ConfigStatic
   265  			default:
   266  				nicInfo.ConfigType = network.ConfigManual
   267  			}
   268  
   269  			if link.IPAddress() == "" {
   270  				logger.Debugf("interface %q has no address", iface.Name())
   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  }