github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/network/environ.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/go-oracle-cloud/api"
    13  	"github.com/juju/go-oracle-cloud/common"
    14  	"github.com/juju/go-oracle-cloud/response"
    15  	"github.com/juju/loggo"
    16  	names "gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/core/instance"
    19  	"github.com/juju/juju/environs"
    20  	"github.com/juju/juju/environs/context"
    21  	"github.com/juju/juju/network"
    22  	commonProvider "github.com/juju/juju/provider/oracle/common"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.provider.oracle.network")
    26  
    27  // NetworkingAPI defines methods needed to interact with the networking features
    28  // of the Oracle API
    29  type NetworkingAPI interface {
    30  	commonProvider.Instancer
    31  	commonProvider.Composer
    32  
    33  	// AllIpNetworks fetches all IP networks matching a filter. A nil valued filter
    34  	// will return all IP networks
    35  	AllIpNetworks([]api.Filter) (response.AllIpNetworks, error)
    36  
    37  	// AllAcls fetches all ACLs that match a given filter.
    38  	AllAcls([]api.Filter) (response.AllAcls, error)
    39  }
    40  
    41  // Environ implements the environs.Networking interface
    42  type Environ struct {
    43  	client NetworkingAPI
    44  
    45  	env commonProvider.OracleInstancer
    46  }
    47  
    48  var _ environs.Networking = (*Environ)(nil)
    49  
    50  // NewEnviron returns a new instance of Environ
    51  func NewEnviron(api NetworkingAPI, env commonProvider.OracleInstancer) *Environ {
    52  	return &Environ{
    53  		client: api,
    54  		env:    env,
    55  	}
    56  }
    57  
    58  // Subnets is defined on the environs.Networking interface.
    59  func (e Environ) Subnets(ctx context.ProviderCallContext, id instance.Id, subnets []network.Id) ([]network.SubnetInfo, error) {
    60  	ret := []network.SubnetInfo{}
    61  	found := make(map[string]bool)
    62  	if id != instance.UnknownId {
    63  		instanceNets, err := e.NetworkInterfaces(ctx, id)
    64  		if err != nil {
    65  			return ret, errors.Trace(err)
    66  		}
    67  		if len(subnets) == 0 {
    68  			for _, val := range instanceNets {
    69  				found[string(val.ProviderSubnetId)] = false
    70  			}
    71  		}
    72  		for _, val := range instanceNets {
    73  			if _, ok := found[string(val.ProviderSubnetId)]; !ok {
    74  				continue
    75  			} else {
    76  				found[string(val.ProviderSubnetId)] = true
    77  				subnetInfo := network.SubnetInfo{
    78  					CIDR:            val.CIDR,
    79  					ProviderId:      val.ProviderSubnetId,
    80  					SpaceProviderId: val.ProviderSpaceId,
    81  				}
    82  				ret = append(ret, subnetInfo)
    83  			}
    84  		}
    85  	} else {
    86  		subnets, err := e.getSubnetInfoAsMap()
    87  		if err != nil {
    88  			return ret, errors.Trace(err)
    89  		}
    90  		if len(subnets) == 0 {
    91  			for key := range subnets {
    92  				found[key] = false
    93  			}
    94  		}
    95  		for key, val := range subnets {
    96  			if _, ok := found[key]; !ok {
    97  				continue
    98  			}
    99  			found[key] = true
   100  			ret = append(ret, val)
   101  		}
   102  	}
   103  	missing := []string{}
   104  	for key, ok := range found {
   105  		if !ok {
   106  			missing = append(missing, key)
   107  		}
   108  	}
   109  	if len(missing) > 0 {
   110  		return nil, errors.Errorf(
   111  			"missing subnets: %s", strings.Join(missing, ","))
   112  	}
   113  	return ret, nil
   114  }
   115  
   116  // getSubnetInfoAsMap will return the subnet information
   117  // for the getSubnetInfo as a map rather than a slice
   118  func (e Environ) getSubnetInfoAsMap() (map[string]network.SubnetInfo, error) {
   119  	subnets, err := e.getSubnetInfo()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	ret := make(map[string]network.SubnetInfo, len(subnets))
   124  	for _, val := range subnets {
   125  		ret[string(val.ProviderId)] = val
   126  	}
   127  	return ret, nil
   128  }
   129  
   130  // getSubnetInfo returns subnet information for all subnets known to
   131  // the oracle provider
   132  func (e Environ) getSubnetInfo() ([]network.SubnetInfo, error) {
   133  	networks, err := e.client.AllIpNetworks(nil)
   134  	if err != nil {
   135  		return nil, errors.Trace(err)
   136  	}
   137  	subnets := make([]network.SubnetInfo, len(networks.Result))
   138  	idx := 0
   139  	for _, val := range networks.Result {
   140  		var spaceId network.Id
   141  		if val.IpNetworkExchange != nil {
   142  			spaceId = network.Id(*val.IpNetworkExchange)
   143  		}
   144  		subnets[idx] = network.SubnetInfo{
   145  			ProviderId:      network.Id(val.Name),
   146  			CIDR:            val.IpAddressPrefix,
   147  			SpaceProviderId: spaceId,
   148  			AvailabilityZones: []string{
   149  				"default",
   150  			},
   151  		}
   152  		idx++
   153  	}
   154  	return subnets, nil
   155  }
   156  
   157  // NetworkInterfaces is defined on the environs.Networking interface.
   158  func (e Environ) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) {
   159  	instance, err := e.env.Details(instId)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	if len(instance.Networking) == 0 {
   165  		return []network.InterfaceInfo{}, nil
   166  	}
   167  	subnetInfo, err := e.getSubnetInfoAsMap()
   168  	if err != nil {
   169  		return nil, errors.Trace(err)
   170  	}
   171  	nicAttributes := e.getNicAttributes(instance)
   172  
   173  	interfaces := make([]network.InterfaceInfo, 0, len(instance.Networking))
   174  	idx := 0
   175  	for nicName, nicObj := range instance.Networking {
   176  		// gsamfira: While the API may hold any alphanumeric NIC name
   177  		// of up to 4 characters, inside an ubuntu instance,
   178  		// the NIC will always be named eth0 (where 0 is the index of the NIC).
   179  		// NICs inside the instance will be ordered in the same way the API
   180  		// returns them; i.e. first element returned by the API will be eth0,
   181  		// second element will be eth1 and so on. Sorting is done
   182  		// alphanumerically it makes sense to use the name that will be
   183  		// seen by the juju agent instead of the name that shows up
   184  		// in the provider.
   185  		// TODO (gsamfira): Check NIC naming in CentOS and Windows.
   186  		name := fmt.Sprintf("eth%s", strconv.Itoa(idx))
   187  		deviceIndex := idx
   188  
   189  		idx += 1
   190  
   191  		deviceAttributes, ok := nicAttributes[nicName]
   192  		if !ok {
   193  			return nil, errors.Errorf(
   194  				"failed to get NIC attributes for %q", nicName)
   195  		}
   196  		mac, ip, err := GetMacAndIP(deviceAttributes.Address)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  		addr := network.NewScopedAddress(ip, network.ScopeCloudLocal)
   201  		nic := network.InterfaceInfo{
   202  			InterfaceName: name,
   203  			DeviceIndex:   deviceIndex,
   204  			ProviderId:    network.Id(deviceAttributes.Id),
   205  			MACAddress:    mac,
   206  			Address:       addr,
   207  			InterfaceType: network.EthernetInterface,
   208  		}
   209  
   210  		// gsamfira: VEthernet NICs are connected to shared networks
   211  		// I have not found a way to interrogate details about the shared
   212  		// networks available inside the oracle cloud. There is some
   213  		// documentation on the matter here:
   214  		//
   215  		// https://docs.oracle.com/cloud-machine/latest/stcomputecs/ELUAP/GUID-8CBE0F4E-E376-4C93-BB56-884836273168.htm
   216  		//
   217  		// but I have not been able to get any information about the
   218  		// shared networks using the resources described there.
   219  		// We only populate Space information for NICs attached to
   220  		// IPNetworks (user defined)
   221  		if nicObj.GetType() == common.VNic {
   222  			nicSubnetDetails := subnetInfo[deviceAttributes.Ipnetwork]
   223  			nic.ProviderSpaceId = nicSubnetDetails.SpaceProviderId
   224  			nic.ProviderSubnetId = nicSubnetDetails.ProviderId
   225  			nic.CIDR = nicSubnetDetails.CIDR
   226  		}
   227  		interfaces = append(interfaces, nic)
   228  	}
   229  
   230  	return interfaces, nil
   231  }
   232  
   233  // getNicAttributes returns all network cards attributes from a oracle instance
   234  func (e Environ) getNicAttributes(instance response.Instance) map[string]response.Network {
   235  	if instance.Attributes.Network == nil {
   236  		return map[string]response.Network{}
   237  	}
   238  	n := len(instance.Attributes.Network)
   239  	ret := make(map[string]response.Network, n)
   240  	for name, obj := range instance.Attributes.Network {
   241  		tmp := strings.TrimPrefix(name, `nimbula_vcable-`)
   242  		ret[tmp] = obj
   243  	}
   244  	return ret
   245  }
   246  
   247  // canAccessNetworkAPI checks whether or not we have access to the necessary
   248  // API endpoints needed for spaces support
   249  func (e *Environ) canAccessNetworkAPI() (bool, error) {
   250  	_, err := e.client.AllAcls(nil)
   251  	if err != nil {
   252  		if api.IsMethodNotAllowed(err) {
   253  			return false, nil
   254  		}
   255  		return false, errors.Trace(err)
   256  	}
   257  	return true, nil
   258  }
   259  
   260  // SupportsSpaces is defined on the environs.Networking interface.
   261  func (e Environ) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) {
   262  	logger.Infof("checking for spaces support")
   263  	access, err := e.canAccessNetworkAPI()
   264  	if err != nil {
   265  		return false, errors.Trace(err)
   266  	}
   267  	if access {
   268  		return true, nil
   269  	}
   270  	return false, nil
   271  }
   272  
   273  // SupportsSpaceDiscovery is defined on the environs.Networking interface.
   274  func (e Environ) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) {
   275  	access, err := e.canAccessNetworkAPI()
   276  	if err != nil {
   277  		return false, errors.Trace(err)
   278  	}
   279  	if access {
   280  		return true, nil
   281  	}
   282  	return false, nil
   283  }
   284  
   285  // SupportsContainerAddresses is defined on the environs.Networking interface.
   286  func (e Environ) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) {
   287  	return false, errors.NotSupportedf("container address allocation")
   288  }
   289  
   290  // AllocateContainerAddresses is defined on the environs.Networking interface.
   291  func (e Environ) AllocateContainerAddresses(
   292  	ctx context.ProviderCallContext,
   293  	hostInstanceID instance.Id,
   294  	containerTag names.MachineTag,
   295  	preparedInfo []network.InterfaceInfo,
   296  ) ([]network.InterfaceInfo, error) {
   297  	return nil, errors.NotSupportedf("containers")
   298  }
   299  
   300  // ReleaseContainerAddresses is defined on the environs.Networking interface.
   301  func (e Environ) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error {
   302  	return errors.NotSupportedf("container")
   303  }
   304  
   305  // Spaces is defined on the environs.Networking interface.
   306  func (e Environ) Spaces(ctx context.ProviderCallContext) ([]network.SpaceInfo, error) {
   307  	networks, err := e.getSubnetInfo()
   308  	if err != nil {
   309  		return nil, errors.Trace(err)
   310  	}
   311  	exchanges := map[string]network.SpaceInfo{}
   312  	for _, val := range networks {
   313  		if val.SpaceProviderId == "" {
   314  			continue
   315  		}
   316  		logger.Infof("found network %s with space %s",
   317  			string(val.ProviderId), string(val.SpaceProviderId))
   318  		providerID := string(val.SpaceProviderId)
   319  		tmp := strings.Split(providerID, `/`)
   320  		name := tmp[len(tmp)-1]
   321  		// Oracle allows us to attach an IP network to a space belonging to
   322  		// another user using the web portal. We recompose the provider ID
   323  		// (which is unique) and compare to the provider ID of the space.
   324  		// If they match, the space belongs to us
   325  		tmpProviderId := e.client.ComposeName(name)
   326  		if tmpProviderId != providerID {
   327  			continue
   328  		}
   329  		if space, ok := exchanges[name]; !ok {
   330  			logger.Infof("creating new space obj for %s and adding %s",
   331  				name, string(val.ProviderId))
   332  			exchanges[name] = network.SpaceInfo{
   333  				Name:       name,
   334  				ProviderId: val.SpaceProviderId,
   335  				Subnets: []network.SubnetInfo{
   336  					val,
   337  				},
   338  			}
   339  		} else {
   340  			logger.Infof("appending subnet %s to %s", string(val.ProviderId), space.Name)
   341  			space.Subnets = append(space.Subnets, val)
   342  			exchanges[name] = space
   343  		}
   344  
   345  	}
   346  	var ret []network.SpaceInfo
   347  	for _, val := range exchanges {
   348  		ret = append(ret, val)
   349  	}
   350  
   351  	logger.Infof("returning spaces: %v", ret)
   352  	return ret, nil
   353  }
   354  
   355  // ProviderSpaceInfo is defined on the environs.NetworkingEnviron interface.
   356  func (Environ) ProviderSpaceInfo(ctx context.ProviderCallContext, space *network.SpaceInfo) (*environs.ProviderSpaceInfo, error) {
   357  	return nil, errors.NotSupportedf("provider space info")
   358  }
   359  
   360  // AreSpacesRoutable is defined on the environs.NetworkingEnviron interface.
   361  func (Environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) {
   362  	return false, nil
   363  }
   364  
   365  // SSHAddresses is defined on the environs.SSHAddresses interface.
   366  func (*Environ) SSHAddresses(ctx context.ProviderCallContext, addresses []network.Address) ([]network.Address, error) {
   367  	return addresses, nil
   368  }
   369  
   370  // SuperSubnets implements environs.SuperSubnets
   371  func (*Environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) {
   372  	return nil, errors.NotSupportedf("super subnets")
   373  }