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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package gce
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"google.golang.org/api/compute/v1"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/core/instance"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/context"
    18  	"github.com/juju/juju/network"
    19  	"github.com/juju/juju/provider/gce/google"
    20  )
    21  
    22  type subnetMap map[string]network.SubnetInfo
    23  type networkMap map[string]*compute.Network
    24  
    25  // Subnets implements environs.NetworkingEnviron.
    26  func (e *environ) Subnets(ctx context.ProviderCallContext, inst instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
    27  	// In GCE all the subnets are in all AZs.
    28  	zones, err := e.zoneNames(ctx)
    29  	if err != nil {
    30  		return nil, errors.Trace(err)
    31  	}
    32  	ids := makeIncludeSet(subnetIds)
    33  	var results []network.SubnetInfo
    34  	if inst == instance.UnknownId {
    35  		results, err = e.getMatchingSubnets(ctx, ids, zones)
    36  	} else {
    37  		results, err = e.getInstanceSubnets(ctx, inst, ids, zones)
    38  	}
    39  	if err != nil {
    40  		return nil, errors.Trace(err)
    41  	}
    42  
    43  	if missing := ids.Missing(); len(missing) != 0 {
    44  		return nil, errors.NotFoundf("subnets %v", formatMissing(missing))
    45  	}
    46  
    47  	return results, nil
    48  }
    49  
    50  func (e *environ) zoneNames(ctx context.ProviderCallContext) ([]string, error) {
    51  	zones, err := e.AvailabilityZones(ctx)
    52  	if err != nil {
    53  		return nil, errors.Trace(err)
    54  	}
    55  	names := make([]string, len(zones))
    56  	for i, zone := range zones {
    57  		names[i] = zone.Name()
    58  	}
    59  	return names, nil
    60  }
    61  
    62  func (e *environ) networksByURL(ctx context.ProviderCallContext) (networkMap, error) {
    63  	networks, err := e.gce.Networks()
    64  	if err != nil {
    65  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
    66  	}
    67  	results := make(networkMap)
    68  	for _, network := range networks {
    69  		results[network.SelfLink] = network
    70  	}
    71  	return results, nil
    72  }
    73  
    74  func (e *environ) getMatchingSubnets(ctx context.ProviderCallContext, subnetIds IncludeSet, zones []string) ([]network.SubnetInfo, error) {
    75  	allSubnets, err := e.gce.Subnetworks(e.cloud.Region)
    76  	if err != nil {
    77  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
    78  	}
    79  	networks, err := e.networksByURL(ctx)
    80  	if err != nil {
    81  		return nil, errors.Trace(err)
    82  	}
    83  	var results []network.SubnetInfo
    84  	for _, subnet := range allSubnets {
    85  		netwk, ok := networks[subnet.Network]
    86  		if !ok {
    87  			return nil, errors.NotFoundf("network %q for subnet %q", subnet.Network, subnet.Name)
    88  		}
    89  		if subnetIds.Include(subnet.Name) {
    90  			results = append(results, makeSubnetInfo(
    91  				network.Id(subnet.Name),
    92  				network.Id(netwk.Name),
    93  				subnet.IpCidrRange,
    94  				zones,
    95  			))
    96  		}
    97  	}
    98  	// We have to include networks in 'LEGACY' mode that do not have subnetworks.
    99  	for _, netwk := range networks {
   100  		if netwk.IPv4Range != "" && subnetIds.Include(netwk.Name) {
   101  			results = append(results, makeSubnetInfo(
   102  				network.Id(netwk.Name),
   103  				network.Id(netwk.Name),
   104  				netwk.IPv4Range,
   105  				zones,
   106  			))
   107  		}
   108  	}
   109  	return results, nil
   110  }
   111  
   112  func (e *environ) getInstanceSubnets(ctx context.ProviderCallContext, inst instance.Id, subnetIds IncludeSet, zones []string) ([]network.SubnetInfo, error) {
   113  	ifaces, err := e.NetworkInterfaces(ctx, inst)
   114  	if err != nil {
   115  		return nil, errors.Trace(err)
   116  	}
   117  	var results []network.SubnetInfo
   118  	for _, iface := range ifaces {
   119  		if subnetIds.Include(string(iface.ProviderSubnetId)) {
   120  			results = append(results, makeSubnetInfo(
   121  				iface.ProviderSubnetId,
   122  				iface.ProviderNetworkId,
   123  				iface.CIDR,
   124  				zones,
   125  			))
   126  		}
   127  	}
   128  	return results, nil
   129  }
   130  
   131  // NetworkInterfaces implements environs.NetworkingEnviron.
   132  func (e *environ) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) {
   133  	insts, err := e.Instances(ctx, []instance.Id{instId})
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  	envInst, ok := insts[0].(*environInstance)
   138  	if !ok {
   139  		// This shouldn't happen.
   140  		return nil, errors.Errorf("couldn't extract google instance for %q", instId)
   141  	}
   142  	// In GCE all the subnets are in all AZs.
   143  	zones, err := e.zoneNames(ctx)
   144  	if err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  	networks, err := e.networksByURL(ctx)
   148  	if err != nil {
   149  		return nil, errors.Trace(err)
   150  	}
   151  	googleInst := envInst.base
   152  	ifaces := googleInst.NetworkInterfaces()
   153  
   154  	var subnetURLs []string
   155  	for _, iface := range ifaces {
   156  		if iface.Subnetwork != "" {
   157  			subnetURLs = append(subnetURLs, iface.Subnetwork)
   158  		}
   159  	}
   160  	subnets, err := e.subnetsByURL(ctx, subnetURLs, networks, zones)
   161  	if err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  	// We know there'll be a subnet for each url requested, otherwise
   165  	// there would have been an error.
   166  
   167  	var results []network.InterfaceInfo
   168  	for i, iface := range ifaces {
   169  		details, err := findNetworkDetails(iface, subnets, networks)
   170  		if err != nil {
   171  			return nil, errors.Annotatef(err, "instance %q", instId)
   172  		}
   173  		results = append(results, network.InterfaceInfo{
   174  			DeviceIndex: i,
   175  			CIDR:        details.cidr,
   176  			// The network interface has no id in GCE so it's
   177  			// identified by the machine's id + its name.
   178  			ProviderId:        network.Id(fmt.Sprintf("%s/%s", instId, iface.Name)),
   179  			ProviderSubnetId:  details.subnet,
   180  			ProviderNetworkId: details.network,
   181  			AvailabilityZones: copyStrings(zones),
   182  			InterfaceName:     iface.Name,
   183  			Address:           network.NewScopedAddress(iface.NetworkIP, network.ScopeCloudLocal),
   184  			InterfaceType:     network.EthernetInterface,
   185  			Disabled:          false,
   186  			NoAutoStart:       false,
   187  			ConfigType:        network.ConfigDHCP,
   188  		})
   189  	}
   190  	return results, nil
   191  }
   192  
   193  type networkDetails struct {
   194  	cidr    string
   195  	subnet  network.Id
   196  	network network.Id
   197  }
   198  
   199  // findNetworkDetails looks up the network information we need to
   200  // populate an InterfaceInfo - if the interface is on a legacy network
   201  // we use information from the network because there'll be no subnet
   202  // linked.
   203  func findNetworkDetails(iface compute.NetworkInterface, subnets subnetMap, networks networkMap) (networkDetails, error) {
   204  	var result networkDetails
   205  	if iface.Subnetwork == "" {
   206  		// This interface is on a legacy network.
   207  		netwk, ok := networks[iface.Network]
   208  		if !ok {
   209  			return result, errors.NotFoundf("network %q", iface.Network)
   210  		}
   211  		result.cidr = netwk.IPv4Range
   212  		result.subnet = ""
   213  		result.network = network.Id(netwk.Name)
   214  	} else {
   215  		subnet, ok := subnets[iface.Subnetwork]
   216  		if !ok {
   217  			return result, errors.NotFoundf("subnet %q", iface.Subnetwork)
   218  		}
   219  		result.cidr = subnet.CIDR
   220  		result.subnet = subnet.ProviderId
   221  		result.network = subnet.ProviderNetworkId
   222  	}
   223  	return result, nil
   224  }
   225  
   226  func (e *environ) subnetsByURL(ctx context.ProviderCallContext, urls []string, networks networkMap, zones []string) (subnetMap, error) {
   227  	if len(urls) == 0 {
   228  		return make(map[string]network.SubnetInfo), nil
   229  	}
   230  	urlSet := includeSet{items: set.NewStrings(urls...)}
   231  	allSubnets, err := e.gce.Subnetworks(e.cloud.Region)
   232  	if err != nil {
   233  		return nil, google.HandleCredentialError(errors.Trace(err), ctx)
   234  	}
   235  	results := make(map[string]network.SubnetInfo)
   236  	for _, subnet := range allSubnets {
   237  		netwk, ok := networks[subnet.Network]
   238  		if !ok {
   239  			return nil, errors.NotFoundf("network %q for subnet %q", subnet.Network, subnet.Name)
   240  		}
   241  		if urlSet.Include(subnet.SelfLink) {
   242  			results[subnet.SelfLink] = makeSubnetInfo(
   243  				network.Id(subnet.Name),
   244  				network.Id(netwk.Name),
   245  				subnet.IpCidrRange,
   246  				zones,
   247  			)
   248  		}
   249  	}
   250  	if missing := urlSet.Missing(); len(missing) != 0 {
   251  		return nil, errors.NotFoundf("subnets %v", formatMissing(missing))
   252  	}
   253  	return results, nil
   254  }
   255  
   256  // SupportsSpaces implements environs.NetworkingEnviron.
   257  func (e *environ) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) {
   258  	return false, nil
   259  }
   260  
   261  // SupportsSpaceDiscovery implements environs.NetworkingEnviron.
   262  func (e *environ) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) {
   263  	return false, nil
   264  }
   265  
   266  // Spaces implements environs.NetworkingEnviron.
   267  func (e *environ) Spaces(ctx context.ProviderCallContext) ([]network.SpaceInfo, error) {
   268  	return nil, errors.NotSupportedf("spaces")
   269  }
   270  
   271  // SupportsContainerAddresses implements environs.NetworkingEnviron.
   272  func (e *environ) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) {
   273  	return false, nil
   274  }
   275  
   276  // AllocateContainerAddresses implements environs.NetworkingEnviron.
   277  func (e *environ) AllocateContainerAddresses(context.ProviderCallContext, instance.Id, names.MachineTag, []network.InterfaceInfo) ([]network.InterfaceInfo, error) {
   278  	return nil, errors.NotSupportedf("container addresses")
   279  }
   280  
   281  // ReleaseContainerAddresses implements environs.NetworkingEnviron.
   282  func (e *environ) ReleaseContainerAddresses(context.ProviderCallContext, []network.ProviderInterfaceInfo) error {
   283  	return errors.NotSupportedf("container addresses")
   284  }
   285  
   286  // ProviderSpaceInfo implements environs.NetworkingEnviron.
   287  func (*environ) ProviderSpaceInfo(ctx context.ProviderCallContext, space *network.SpaceInfo) (*environs.ProviderSpaceInfo, error) {
   288  	return nil, errors.NotSupportedf("provider space info")
   289  }
   290  
   291  // AreSpacesRoutable implements environs.NetworkingEnviron.
   292  func (*environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) {
   293  	return false, nil
   294  }
   295  
   296  // SSHAddresses implements environs.SSHAddresses.
   297  // For GCE we want to make sure we're returning only one public address, so that probing won't
   298  // cause SSHGuard to lock us out
   299  func (*environ) SSHAddresses(ctx context.ProviderCallContext, addresses []network.Address) ([]network.Address, error) {
   300  	bestAddress, ok := network.SelectPublicAddress(addresses)
   301  	if ok {
   302  		return []network.Address{bestAddress}, nil
   303  	} else {
   304  		// fallback
   305  		return addresses, nil
   306  	}
   307  }
   308  
   309  // SuperSubnets implements environs.SuperSubnets
   310  func (e *environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) {
   311  	subnets, err := e.Subnets(ctx, "", nil)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	cidrs := make([]string, len(subnets))
   316  	for i, subnet := range subnets {
   317  		cidrs[i] = subnet.CIDR
   318  	}
   319  	return cidrs, nil
   320  }
   321  
   322  func copyStrings(items []string) []string {
   323  	if items == nil {
   324  		return nil
   325  	}
   326  	result := make([]string, len(items))
   327  	copy(result, items)
   328  	return result
   329  }
   330  
   331  func makeSubnetInfo(subnetId network.Id, networkId network.Id, cidr string, zones []string) network.SubnetInfo {
   332  	return network.SubnetInfo{
   333  		ProviderId:        subnetId,
   334  		ProviderNetworkId: networkId,
   335  		CIDR:              cidr,
   336  		AvailabilityZones: copyStrings(zones),
   337  		VLANTag:           0,
   338  		SpaceProviderId:   "",
   339  	}
   340  }
   341  
   342  // IncludeSet represents a set of items that can be crossed off once,
   343  // and when you're finished crossing items off then you can see what's
   344  // left.
   345  type IncludeSet interface {
   346  	// Include returns whether this item should be included, and
   347  	// crosses it off.
   348  	Include(item string) bool
   349  	// Missing returns any items that haven't been crossed off (as a
   350  	// sorted slice).
   351  	Missing() []string
   352  }
   353  
   354  // includeAny allows any items and doesn't report any as missing.
   355  type includeAny struct{}
   356  
   357  // Include implements IncludeSet.
   358  func (includeAny) Include(string) bool { return true }
   359  
   360  // Missing implements IncludeSet.
   361  func (includeAny) Missing() []string { return nil }
   362  
   363  // includeSet is a set of items that we want to find in some results.
   364  type includeSet struct {
   365  	items set.Strings
   366  }
   367  
   368  // Include implements IncludeSet.
   369  func (s *includeSet) Include(item string) bool {
   370  	if s.items.Contains(item) {
   371  		s.items.Remove(item)
   372  		return true
   373  	}
   374  	return false
   375  }
   376  
   377  // Missing implements IncludeSet.
   378  func (s *includeSet) Missing() []string {
   379  	return s.items.SortedValues()
   380  }
   381  
   382  func makeIncludeSet(ids []network.Id) IncludeSet {
   383  	if len(ids) == 0 {
   384  		return &includeAny{}
   385  	}
   386  	strings := set.NewStrings()
   387  	for _, id := range ids {
   388  		strings.Add(string(id))
   389  	}
   390  	return &includeSet{items: strings}
   391  }
   392  
   393  func formatMissing(items []string) string {
   394  	parts := make([]string, len(items))
   395  	for i, item := range items {
   396  		parts[i] = fmt.Sprintf("%q", item)
   397  	}
   398  	return "[" + strings.Join(parts, ", ") + "]"
   399  }