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

     1  // Copyright 2012-2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"fmt"
     8  	"net"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/utils"
    15  	"gopkg.in/goose.v2/neutron"
    16  	"gopkg.in/goose.v2/nova"
    17  
    18  	"github.com/juju/juju/core/instance"
    19  	"github.com/juju/juju/network"
    20  )
    21  
    22  // Networking is an interface providing networking-related operations
    23  // for an OpenStack Environ.
    24  type Networking interface {
    25  	// AllocatePublicIP allocates a public (floating) IP
    26  	// to the specified instance.
    27  	AllocatePublicIP(instance.Id) (*string, error)
    28  
    29  	// DefaultNetworks returns the set of networks that should be
    30  	// added by default to all new instances.
    31  	DefaultNetworks() ([]nova.ServerNetworks, error)
    32  
    33  	// ResolveNetwork takes either a network ID or label
    34  	// with a string to specify whether the network is external
    35  	// and returns the corresponding network ID.
    36  	ResolveNetwork(string, bool) (string, error)
    37  
    38  	// Subnets returns basic information about subnets known
    39  	// by OpenStack for the environment.
    40  	// Needed for Environ.Networking
    41  	Subnets(instance.Id, []network.Id) ([]network.SubnetInfo, error)
    42  
    43  	// NetworkInterfaces requests information about the network
    44  	// interfaces on the given instance.
    45  	// Needed for Environ.Networking
    46  	NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error)
    47  }
    48  
    49  // NetworkingDecorator is an interface that provides a means of overriding
    50  // the default Networking implementation.
    51  type NetworkingDecorator interface {
    52  	// DecorateNetworking can be used to return a new Networking
    53  	// implementation that overrides the provided, default Networking
    54  	// implementation.
    55  	DecorateNetworking(Networking) (Networking, error)
    56  }
    57  
    58  // switchingNetworking is an implementation of Networking that delegates
    59  // to either Neutron networking (preferred), or legacy Nova networking if
    60  // there is no support for Neutron.
    61  type switchingNetworking struct {
    62  	env *Environ
    63  
    64  	mu         sync.Mutex
    65  	networking Networking
    66  }
    67  
    68  func (n *switchingNetworking) initNetworking() error {
    69  	n.mu.Lock()
    70  	defer n.mu.Unlock()
    71  	if n.networking != nil {
    72  		return nil
    73  	}
    74  
    75  	client := n.env.client()
    76  	if !client.IsAuthenticated() {
    77  		if err := authenticateClient(client); err != nil {
    78  			return errors.Trace(err)
    79  		}
    80  	}
    81  
    82  	base := networkingBase{env: n.env}
    83  	if n.env.supportsNeutron() {
    84  		n.networking = &NeutronNetworking{base}
    85  	} else {
    86  		n.networking = &LegacyNovaNetworking{base}
    87  	}
    88  	return nil
    89  }
    90  
    91  // AllocatePublicIP is part of the Networking interface.
    92  func (n *switchingNetworking) AllocatePublicIP(instId instance.Id) (*string, error) {
    93  	if err := n.initNetworking(); err != nil {
    94  		return nil, errors.Trace(err)
    95  	}
    96  	return n.networking.AllocatePublicIP(instId)
    97  }
    98  
    99  // DefaultNetworks is part of the Networking interface.
   100  func (n *switchingNetworking) DefaultNetworks() ([]nova.ServerNetworks, error) {
   101  	if err := n.initNetworking(); err != nil {
   102  		return nil, errors.Trace(err)
   103  	}
   104  	return n.networking.DefaultNetworks()
   105  }
   106  
   107  // ResolveNetwork is part of the Networking interface.
   108  func (n *switchingNetworking) ResolveNetwork(name string, external bool) (string, error) {
   109  	if err := n.initNetworking(); err != nil {
   110  		return "", errors.Trace(err)
   111  	}
   112  	return n.networking.ResolveNetwork(name, external)
   113  }
   114  
   115  // Subnets is part of the Networking interface.
   116  func (n *switchingNetworking) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
   117  	if err := n.initNetworking(); err != nil {
   118  		return nil, errors.Trace(err)
   119  	}
   120  	return n.networking.Subnets(instId, subnetIds)
   121  }
   122  
   123  // NetworkInterfaces is part of the Networking interface
   124  func (n *switchingNetworking) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) {
   125  	if err := n.initNetworking(); err != nil {
   126  		return nil, errors.Trace(err)
   127  	}
   128  	return n.networking.NetworkInterfaces(instId)
   129  }
   130  
   131  type networkingBase struct {
   132  	env *Environ
   133  }
   134  
   135  func processResolveNetworkIds(name string, networkIds []string) (string, error) {
   136  	switch len(networkIds) {
   137  	case 1:
   138  		return networkIds[0], nil
   139  	case 0:
   140  		return "", errors.Errorf("no networks exist with label %q", name)
   141  	}
   142  	return "", errors.Errorf("multiple networks with label %q: %v", name, networkIds)
   143  }
   144  
   145  // NeutronNetworking is an implementation of Networking that uses the Neutron
   146  // network APIs.
   147  type NeutronNetworking struct {
   148  	networkingBase
   149  }
   150  
   151  // networkFilter returns a neutron.Filter to match Neutron Networks with
   152  // the exact given name AND router:external boolean result.
   153  func projectIdFilter(projectId string) *neutron.Filter {
   154  	filter := neutron.NewFilter()
   155  	filter.Set(neutron.FilterProjectId, projectId)
   156  	return filter
   157  }
   158  
   159  // AllocatePublicIP is part of the Networking interface.
   160  func (n *NeutronNetworking) AllocatePublicIP(instId instance.Id) (*string, error) {
   161  	extNetworkIds := make([]string, 0)
   162  	neutronClient := n.env.neutron()
   163  	externalNetwork := n.env.ecfg().externalNetwork()
   164  	if externalNetwork != "" {
   165  		// the config specified an external network, try it first.
   166  		netId, err := resolveNeutronNetwork(neutronClient, externalNetwork, true)
   167  		if err != nil {
   168  			logger.Debugf("external network %s not found, search for one", externalNetwork)
   169  		} else {
   170  			logger.Debugf("using external network %q", externalNetwork)
   171  			extNetworkIds = []string{netId}
   172  		}
   173  	}
   174  
   175  	if len(extNetworkIds) == 0 {
   176  		// Create slice of network.Ids for external networks in the same AZ as
   177  		// the instance's network, to find an existing floating ip in, or allocate
   178  		// a new floating ip from.
   179  		network := n.env.ecfg().network()
   180  		netId, err := resolveNeutronNetwork(neutronClient, network, false)
   181  		netDetails, err := neutronClient.GetNetworkV2(netId)
   182  		if err != nil {
   183  			return nil, errors.Trace(err)
   184  		}
   185  
   186  		for _, az := range netDetails.AvailabilityZones {
   187  			extNetIds, _ := getExternalNeutronNetworksByAZ(n.env, az)
   188  			if len(extNetIds) > 0 {
   189  				extNetworkIds = append(extNetworkIds, extNetIds...)
   190  			}
   191  		}
   192  
   193  		if len(extNetworkIds) == 0 {
   194  			return nil, errors.NewNotFound(nil, fmt.Sprintf("could not find an external network in availability zone %s", netDetails.AvailabilityZones))
   195  		}
   196  	}
   197  
   198  	// Look for FIPs in same project as the credentials.
   199  	// Admins have visibility into other projects.
   200  	fips, err := n.env.neutron().ListFloatingIPsV2(projectIdFilter(n.env.client().TenantId()))
   201  	if err != nil {
   202  		return nil, errors.Trace(err)
   203  	}
   204  
   205  	// Is there an unused FloatingIP on an external network in the instance's availability zone?
   206  	for _, fip := range fips {
   207  		if fip.FixedIP == "" {
   208  			// Not a perfect solution.  If an external network was specified in the
   209  			// config, it'll be at the top of the extNetworkIds, but may be not used
   210  			// if the available FIP isn't it in.  However the instance and the
   211  			// FIP will be in the same availability zone.
   212  			for _, extNetId := range extNetworkIds {
   213  				if fip.FloatingNetworkId == extNetId {
   214  					logger.Debugf("found unassigned public ip: %v", fip.IP)
   215  					return &fip.IP, nil
   216  				}
   217  			}
   218  		}
   219  	}
   220  
   221  	// allocate a new IP and use it
   222  	var lastErr error
   223  	for _, extNetId := range extNetworkIds {
   224  		var newfip *neutron.FloatingIPV2
   225  		newfip, lastErr = neutronClient.AllocateFloatingIPV2(extNetId)
   226  		if lastErr == nil {
   227  			logger.Debugf("allocated new public IP: %s", newfip.IP)
   228  			return &newfip.IP, nil
   229  		}
   230  	}
   231  
   232  	logger.Debugf("Unable to allocate a public IP")
   233  	return nil, lastErr
   234  }
   235  
   236  // externalNetworkFilter returns a neutron.Filter to match Neutron Networks with
   237  // router:external = true.
   238  func externalNetworkFilter() *neutron.Filter {
   239  	filter := neutron.NewFilter()
   240  	filter.Set(neutron.FilterRouterExternal, "true")
   241  	return filter
   242  }
   243  
   244  // getExternalNeutronNetworksByAZ returns all external networks within the
   245  // given availability zone. If azName is empty, return all external networks.
   246  func getExternalNeutronNetworksByAZ(e *Environ, azName string) ([]string, error) {
   247  	neutron := e.neutron()
   248  	// Find all external networks in availability zone
   249  	networks, err := neutron.ListNetworksV2(externalNetworkFilter())
   250  	if err != nil {
   251  		return nil, errors.Trace(err)
   252  	}
   253  	netIds := make([]string, 0)
   254  	for _, network := range networks {
   255  		for _, netAZ := range network.AvailabilityZones {
   256  			if azName == netAZ {
   257  				netIds = append(netIds, network.Id)
   258  				break
   259  			}
   260  		}
   261  	}
   262  	if len(netIds) == 0 {
   263  		return nil, errors.NewNotFound(nil, "No External networks found to allocate a Floating IP")
   264  	}
   265  	return netIds, nil
   266  }
   267  
   268  // DefaultNetworks is part of the Networking interface.
   269  func (n *NeutronNetworking) DefaultNetworks() ([]nova.ServerNetworks, error) {
   270  	return []nova.ServerNetworks{}, nil
   271  }
   272  
   273  // ResolveNetwork is part of the Networking interface.
   274  func (n *NeutronNetworking) ResolveNetwork(name string, external bool) (string, error) {
   275  	return resolveNeutronNetwork(n.env.neutron(), name, external)
   276  }
   277  
   278  // networkFilter returns a neutron.Filter to match Neutron Networks with
   279  // the exact given name AND router:external boolean result.
   280  func networkFilter(name string, external bool) *neutron.Filter {
   281  	filter := neutron.NewFilter()
   282  	filter.Set(neutron.FilterNetwork, fmt.Sprintf("%s", name))
   283  	filter.Set(neutron.FilterRouterExternal, fmt.Sprintf("%t", external))
   284  	return filter
   285  }
   286  
   287  func resolveNeutronNetwork(neutron *neutron.Client, name string, external bool) (string, error) {
   288  	if utils.IsValidUUIDString(name) {
   289  		return name, nil
   290  	}
   291  	networks, err := neutron.ListNetworksV2(networkFilter(name, external))
   292  	if err != nil {
   293  		return "", err
   294  	}
   295  	var networkIds []string
   296  	for _, network := range networks {
   297  		networkIds = append(networkIds, network.Id)
   298  	}
   299  	return processResolveNetworkIds(name, networkIds)
   300  }
   301  
   302  func makeSubnetInfo(neutron *neutron.Client, subnet neutron.SubnetV2) (network.SubnetInfo, error) {
   303  	_, _, err := net.ParseCIDR(subnet.Cidr)
   304  	if err != nil {
   305  		return network.SubnetInfo{}, errors.Annotatef(err, "skipping subnet %q, invalid CIDR", subnet.Cidr)
   306  	}
   307  	net, err := neutron.GetNetworkV2(subnet.NetworkId)
   308  	if err != nil {
   309  		return network.SubnetInfo{}, err
   310  	}
   311  
   312  	// TODO (hml) 2017-03-20:
   313  	// With goose updates, VLANTag can be updated to be
   314  	// network.segmentation_id, if network.network_type equals vlan
   315  	info := network.SubnetInfo{
   316  		CIDR:              subnet.Cidr,
   317  		ProviderId:        network.Id(subnet.Id),
   318  		VLANTag:           0,
   319  		AvailabilityZones: net.AvailabilityZones,
   320  		SpaceProviderId:   "",
   321  	}
   322  	logger.Tracef("found subnet with info %#v", info)
   323  	return info, nil
   324  }
   325  
   326  // Subnets returns basic information about the specified subnets known
   327  // by the provider for the specified instance or list of ids. subnetIds can be
   328  // empty, in which case all known are returned.
   329  func (n *NeutronNetworking) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) {
   330  	netIds := set.NewStrings()
   331  	neutron := n.env.neutron()
   332  	internalNet := n.env.ecfg().network()
   333  	netId, err := resolveNeutronNetwork(neutron, internalNet, false)
   334  	if err != nil {
   335  		// Note: (jam 2018-05-23) We don't treat this as fatal because we used to never pay attention to it anyway
   336  		if internalNet == "" {
   337  			logger.Warningf(noNetConfigMsg(err))
   338  		} else {
   339  			logger.Warningf("could not resolve internal network id for %q: %v", internalNet, err)
   340  		}
   341  	} else {
   342  		netIds.Add(netId)
   343  		// Note, there are cases where we will detect an external
   344  		// network without it being explicitly configured by the user.
   345  		// When we get to a point where we start detecting spaces for users
   346  		// on Openstack, we'll probably need to include better logic here.
   347  		externalNet := n.env.ecfg().externalNetwork()
   348  		if externalNet != "" {
   349  			netId, err := resolveNeutronNetwork(neutron, externalNet, true)
   350  			if err != nil {
   351  				logger.Warningf("could not resolve external network id for %q: %v", externalNet, err)
   352  			} else {
   353  				netIds.Add(netId)
   354  			}
   355  		}
   356  	}
   357  	logger.Debugf("finding subnets in networks: %s", strings.Join(netIds.Values(), ", "))
   358  
   359  	subIdSet := set.NewStrings()
   360  	for _, subId := range subnetIds {
   361  		subIdSet.Add(string(subId))
   362  	}
   363  
   364  	var results []network.SubnetInfo
   365  	if instId != instance.UnknownId {
   366  		// TODO(hml): 2017-03-20
   367  		// Implement Subnets() for case where instId is specified
   368  		return nil, errors.NotSupportedf("neutron subnets with instance Id")
   369  	} else {
   370  		// TODO(jam): 2018-05-23 It is likely that ListSubnetsV2 could
   371  		// take a Filter rather that doing the filtering client side.
   372  		subnets, err := neutron.ListSubnetsV2()
   373  		if err != nil {
   374  			return nil, errors.Annotatef(err, "failed to retrieve subnets")
   375  		}
   376  		if len(subnetIds) == 0 {
   377  			for _, subnet := range subnets {
   378  				// TODO (manadart 2018-07-17): If there was an error resolving
   379  				// an internal network ID, then no subnets will be discovered.
   380  				// The user will get an error attempting to add machines to
   381  				// this model and will have to update model config with a
   382  				// network name; but this does not re-discover the subnets.
   383  				// If subnets/spaces become important, we will have to address
   384  				// this somehow.
   385  				if !netIds.Contains(subnet.NetworkId) {
   386  					logger.Tracef("ignoring subnet %q, part of network %q", subnet.Id, subnet.NetworkId)
   387  					continue
   388  				}
   389  				subIdSet.Add(subnet.Id)
   390  			}
   391  		}
   392  		for _, subnet := range subnets {
   393  			if !subIdSet.Contains(subnet.Id) {
   394  				logger.Tracef("subnet %q not in %v, skipping", subnet.Id, subnetIds)
   395  				continue
   396  			}
   397  			subIdSet.Remove(subnet.Id)
   398  			if info, err := makeSubnetInfo(neutron, subnet); err == nil {
   399  				// Error will already have been logged.
   400  				results = append(results, info)
   401  			}
   402  		}
   403  	}
   404  	if !subIdSet.IsEmpty() {
   405  		return nil, errors.Errorf("failed to find the following subnet ids: %v", subIdSet.Values())
   406  	}
   407  	return results, nil
   408  }
   409  
   410  // noNetConfigMsg is used to present resolution options when an error is
   411  // encountered due to missing "network" configuration.
   412  // Any error from attempting to resolve a network without network
   413  // config set, is likely due to the resolution returning multiple
   414  // internal networks.
   415  func noNetConfigMsg(err error) string {
   416  	return fmt.Sprintf(
   417  		"%s\n\tTo resolve this error, set a value for \"network\" in model-config or model-defaults;"+
   418  			"\n\tor supply it via --config when creating a new model",
   419  		err.Error())
   420  }
   421  
   422  func (n *NeutronNetworking) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) {
   423  	return nil, errors.NotSupportedf("neutron network interfaces")
   424  }