github.com/cilium/cilium@v1.16.2/pkg/alibabacloud/api/api.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package api
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net"
    11  	"time"
    12  
    13  	httperr "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
    14  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
    15  	"github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
    16  	"github.com/aliyun/alibaba-cloud-sdk-go/services/vpc"
    17  	"k8s.io/apimachinery/pkg/util/wait"
    18  
    19  	eniTypes "github.com/cilium/cilium/pkg/alibabacloud/eni/types"
    20  	"github.com/cilium/cilium/pkg/alibabacloud/types"
    21  	"github.com/cilium/cilium/pkg/api/helpers"
    22  	"github.com/cilium/cilium/pkg/cidr"
    23  	ipamTypes "github.com/cilium/cilium/pkg/ipam/types"
    24  	"github.com/cilium/cilium/pkg/spanstat"
    25  )
    26  
    27  const (
    28  	VPCID = "VPCID"
    29  )
    30  
    31  var maxAttachRetries = wait.Backoff{
    32  	Duration: 2500 * time.Millisecond,
    33  	Factor:   1,
    34  	Jitter:   0.1,
    35  	Steps:    6,
    36  	Cap:      0,
    37  }
    38  
    39  // Client an AlibabaCloud API client
    40  type Client struct {
    41  	vpcClient  *vpc.Client
    42  	ecsClient  *ecs.Client
    43  	limiter    *helpers.APILimiter
    44  	metricsAPI MetricsAPI
    45  	filters    map[string]string
    46  }
    47  
    48  // MetricsAPI represents the metrics maintained by the AlibabaCloud API client
    49  type MetricsAPI interface {
    50  	helpers.MetricsAPI
    51  	ObserveAPICall(call, status string, duration float64)
    52  }
    53  
    54  // NewClient create the client
    55  func NewClient(vpcClient *vpc.Client, client *ecs.Client, metrics MetricsAPI, rateLimit float64, burst int, filters map[string]string) *Client {
    56  	return &Client{
    57  		vpcClient:  vpcClient,
    58  		ecsClient:  client,
    59  		limiter:    helpers.NewAPILimiter(metrics, rateLimit, burst),
    60  		metricsAPI: metrics,
    61  		filters:    filters,
    62  	}
    63  }
    64  
    65  // GetInstance returns the instance including its ENIs by the given instanceID
    66  func (c *Client) GetInstance(ctx context.Context, vpcs ipamTypes.VirtualNetworkMap, subnets ipamTypes.SubnetMap, instanceID string) (*ipamTypes.Instance, error) {
    67  	instance := ipamTypes.Instance{}
    68  	instance.Interfaces = map[string]ipamTypes.InterfaceRevision{}
    69  
    70  	networkInterfaceSets, err := c.describeNetworkInterfacesByInstance(ctx, instanceID)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	for _, iface := range networkInterfaceSets {
    76  		ifId := iface.NetworkInterfaceId
    77  		_, eni, err := parseENI(&iface, vpcs, subnets)
    78  		if err != nil {
    79  			return nil, err
    80  		}
    81  
    82  		instance.Interfaces[ifId] = ipamTypes.InterfaceRevision{
    83  			Resource: eni,
    84  		}
    85  	}
    86  	return &instance, nil
    87  }
    88  
    89  // GetInstances returns the list of all instances including their ENIs as instanceMap
    90  func (c *Client) GetInstances(ctx context.Context, vpcs ipamTypes.VirtualNetworkMap, subnets ipamTypes.SubnetMap) (*ipamTypes.InstanceMap, error) {
    91  	instances := ipamTypes.NewInstanceMap()
    92  
    93  	networkInterfaceSets, err := c.describeNetworkInterfaces(ctx)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	for _, iface := range networkInterfaceSets {
    99  		id, eni, err := parseENI(&iface, vpcs, subnets)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  
   104  		instances.Update(id, ipamTypes.InterfaceRevision{
   105  			Resource: eni,
   106  		})
   107  	}
   108  	return instances, nil
   109  }
   110  
   111  // GetVSwitches returns all ecs vSwitches as a subnetMap
   112  func (c *Client) GetVSwitches(ctx context.Context) (ipamTypes.SubnetMap, error) {
   113  	var result ipamTypes.SubnetMap
   114  	for i := 1; ; {
   115  		req := vpc.CreateDescribeVSwitchesRequest()
   116  		req.PageNumber = requests.NewInteger(i)
   117  		req.PageSize = requests.NewInteger(50)
   118  		c.limiter.Limit(ctx, "DescribeVSwitches")
   119  		resp, err := c.vpcClient.DescribeVSwitches(req)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  		if len(resp.VSwitches.VSwitch) == 0 {
   124  			break
   125  		}
   126  		if result == nil {
   127  			result = make(ipamTypes.SubnetMap, resp.TotalCount)
   128  		}
   129  
   130  		for _, v := range resp.VSwitches.VSwitch {
   131  			_, ipnet, err := net.ParseCIDR(v.CidrBlock)
   132  			if err != nil {
   133  				return nil, err
   134  			}
   135  			result[v.VSwitchId] = &ipamTypes.Subnet{
   136  				ID:                 v.VSwitchId,
   137  				Name:               v.VSwitchName,
   138  				CIDR:               cidr.NewCIDR(ipnet),
   139  				AvailabilityZone:   v.ZoneId,
   140  				VirtualNetworkID:   v.VpcId,
   141  				AvailableAddresses: int(v.AvailableIpAddressCount),
   142  				Tags:               map[string]string{},
   143  			}
   144  			for _, tag := range v.Tags.Tag {
   145  				result[v.VSwitchId].Tags[tag.Key] = tag.Value
   146  			}
   147  		}
   148  		if resp.TotalCount < resp.PageNumber*resp.PageSize {
   149  			break
   150  		}
   151  		i++
   152  	}
   153  
   154  	return result, nil
   155  }
   156  
   157  // GetVPC get vpc by id
   158  func (c *Client) GetVPC(ctx context.Context, vpcID string) (*ipamTypes.VirtualNetwork, error) {
   159  	req := vpc.CreateDescribeVpcsRequest()
   160  	req.VpcId = vpcID
   161  	c.limiter.Limit(ctx, "DescribeVpcs")
   162  	resp, err := c.vpcClient.DescribeVpcs(req)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	if len(resp.Vpcs.Vpc) == 0 {
   167  		return nil, fmt.Errorf("cannot find VPC by ID %s", vpcID)
   168  	}
   169  
   170  	return &ipamTypes.VirtualNetwork{
   171  		ID:          resp.Vpcs.Vpc[0].VpcId,
   172  		PrimaryCIDR: resp.Vpcs.Vpc[0].CidrBlock,
   173  		CIDRs:       resp.Vpcs.Vpc[0].SecondaryCidrBlocks.SecondaryCidrBlock,
   174  	}, nil
   175  }
   176  
   177  // GetVPCs retrieves and returns all VPCs
   178  func (c *Client) GetVPCs(ctx context.Context) (ipamTypes.VirtualNetworkMap, error) {
   179  	var result ipamTypes.VirtualNetworkMap
   180  	for i := 1; ; {
   181  		req := vpc.CreateDescribeVpcsRequest()
   182  		req.PageNumber = requests.NewInteger(i)
   183  		req.PageSize = requests.NewInteger(50)
   184  		resp, err := c.vpcClient.DescribeVpcs(req)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  		if len(resp.Vpcs.Vpc) == 0 {
   189  			break
   190  		}
   191  		if result == nil {
   192  			result = make(ipamTypes.VirtualNetworkMap, resp.TotalCount)
   193  		}
   194  		for _, v := range resp.Vpcs.Vpc {
   195  			result[v.VpcId] = &ipamTypes.VirtualNetwork{
   196  				ID:          v.VpcId,
   197  				PrimaryCIDR: v.CidrBlock,
   198  				CIDRs:       v.SecondaryCidrBlocks.SecondaryCidrBlock,
   199  			}
   200  		}
   201  		if resp.TotalCount < resp.PageNumber*resp.PageSize {
   202  			break
   203  		}
   204  		i++
   205  	}
   206  	return result, nil
   207  }
   208  
   209  // GetInstanceTypes returns all the known ECS instance types in the configured region
   210  func (c *Client) GetInstanceTypes(ctx context.Context) ([]ecs.InstanceType, error) {
   211  	var result []ecs.InstanceType
   212  	req := ecs.CreateDescribeInstanceTypesRequest()
   213  	// When there are many instance types, some instance limits can not be queried,
   214  	// so use NextToken and MaxResults for paging query.
   215  	// MaxResults is the number of entries on each page, the maximum value of this parameter is 100.
   216  	// Ref: https://www.alibabacloud.com/help/en/elastic-compute-service/latest/describeinstancetypes
   217  	req.MaxResults = requests.NewInteger(100)
   218  	for {
   219  		resp, err := c.ecsClient.DescribeInstanceTypes(req)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  
   224  		result = append(result, resp.InstanceTypes.InstanceType...)
   225  
   226  		if resp.NextToken == "" {
   227  			break
   228  		}
   229  		req.NextToken = resp.NextToken
   230  	}
   231  
   232  	return result, nil
   233  }
   234  
   235  // GetSecurityGroups return all sg
   236  func (c *Client) GetSecurityGroups(ctx context.Context) (types.SecurityGroupMap, error) {
   237  	var result types.SecurityGroupMap
   238  	for i := 1; ; {
   239  		req := ecs.CreateDescribeSecurityGroupsRequest()
   240  		req.PageNumber = requests.NewInteger(i)
   241  		req.PageSize = requests.NewInteger(50)
   242  		resp, err := c.ecsClient.DescribeSecurityGroups(req)
   243  		if err != nil {
   244  			return nil, err
   245  		}
   246  		if len(resp.SecurityGroups.SecurityGroup) == 0 {
   247  			break
   248  		}
   249  		if result == nil {
   250  			result = make(types.SecurityGroupMap, resp.TotalCount)
   251  		}
   252  		for _, v := range resp.SecurityGroups.SecurityGroup {
   253  			result[v.VpcId] = &types.SecurityGroup{
   254  				ID:    v.SecurityGroupId,
   255  				VPCID: v.VpcId,
   256  				Tags:  parseECSTags(v.Tags.Tag),
   257  			}
   258  		}
   259  		if resp.TotalCount < resp.PageNumber*resp.PageSize {
   260  			break
   261  		}
   262  		i++
   263  	}
   264  	return result, nil
   265  }
   266  
   267  // DescribeNetworkInterface get ENI by id
   268  func (c *Client) DescribeNetworkInterface(ctx context.Context, eniID string) (*ecs.NetworkInterfaceSet, error) {
   269  	req := ecs.CreateDescribeNetworkInterfacesRequest()
   270  	req.NetworkInterfaceId = &[]string{eniID}
   271  	resp, err := c.ecsClient.DescribeNetworkInterfaces(req)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	if len(resp.NetworkInterfaceSets.NetworkInterfaceSet) == 0 {
   276  		return nil, fmt.Errorf("failed to find eni %s", eniID)
   277  	}
   278  	return &resp.NetworkInterfaceSets.NetworkInterfaceSet[0], nil
   279  }
   280  
   281  // CreateNetworkInterface creates an ENI with the given parameters
   282  func (c *Client) CreateNetworkInterface(ctx context.Context, secondaryPrivateIPCount int, vSwitchID string, groups []string, tags map[string]string) (string, *eniTypes.ENI, error) {
   283  	req := ecs.CreateCreateNetworkInterfaceRequest()
   284  	// SecondaryPrivateIpAddressCount is optional but must not be zero
   285  	if secondaryPrivateIPCount > 0 {
   286  		req.SecondaryPrivateIpAddressCount = requests.NewInteger(secondaryPrivateIPCount)
   287  	}
   288  	req.VSwitchId = vSwitchID
   289  	req.SecurityGroupIds = &groups
   290  	reqTag := make([]ecs.CreateNetworkInterfaceTag, 0, len(tags))
   291  	for k, v := range tags {
   292  		reqTag = append(reqTag, ecs.CreateNetworkInterfaceTag{
   293  			Key:   k,
   294  			Value: v,
   295  		})
   296  	}
   297  	req.Tag = &reqTag
   298  
   299  	c.limiter.Limit(ctx, "CreateNetworkInterface")
   300  
   301  	sinceStart := spanstat.Start()
   302  	resp, err := c.ecsClient.CreateNetworkInterface(req)
   303  	c.metricsAPI.ObserveAPICall("CreateNetworkInterface", deriveStatus(err), sinceStart.Seconds())
   304  	if err != nil {
   305  		return "", nil, err
   306  	}
   307  
   308  	var privateIPSets []eniTypes.PrivateIPSet
   309  	for _, p := range resp.PrivateIpSets.PrivateIpSet {
   310  		privateIPSets = append(privateIPSets, eniTypes.PrivateIPSet{
   311  			Primary:          p.Primary,
   312  			PrivateIpAddress: p.PrivateIpAddress,
   313  		})
   314  	}
   315  	eni := &eniTypes.ENI{
   316  		NetworkInterfaceID: resp.NetworkInterfaceId,
   317  		MACAddress:         resp.MacAddress,
   318  		Type:               resp.Type,
   319  		SecurityGroupIDs:   resp.SecurityGroupIds.SecurityGroupId,
   320  		VPC: eniTypes.VPC{
   321  			VPCID: resp.VpcId,
   322  		},
   323  		ZoneID: resp.ZoneId,
   324  		VSwitch: eniTypes.VSwitch{
   325  			VSwitchID: resp.VSwitchId,
   326  		},
   327  		PrimaryIPAddress: resp.PrivateIpAddress,
   328  		PrivateIPSets:    privateIPSets,
   329  		Tags:             parseECSTags(resp.Tags.Tag),
   330  	}
   331  	return resp.NetworkInterfaceId, eni, nil
   332  }
   333  
   334  // AttachNetworkInterface attaches a previously created ENI to an instance
   335  func (c *Client) AttachNetworkInterface(ctx context.Context, instanceID, eniID string) error {
   336  	req := ecs.CreateAttachNetworkInterfaceRequest()
   337  	req.InstanceId = instanceID
   338  	req.NetworkInterfaceId = eniID
   339  	c.limiter.Limit(ctx, "AttachNetworkInterface")
   340  	sinceStart := spanstat.Start()
   341  	_, err := c.ecsClient.AttachNetworkInterface(req)
   342  	c.metricsAPI.ObserveAPICall("AttachNetworkInterface", deriveStatus(err), sinceStart.Seconds())
   343  	if err != nil {
   344  		return err
   345  	}
   346  	return nil
   347  }
   348  
   349  // WaitENIAttached check ENI is attached to ECS and return attached ECS instanceID
   350  func (c *Client) WaitENIAttached(ctx context.Context, eniID string) (string, error) {
   351  	instanceID := ""
   352  	err := wait.ExponentialBackoffWithContext(ctx, maxAttachRetries, func(ctx context.Context) (done bool, err error) {
   353  		eni, err := c.DescribeNetworkInterface(ctx, eniID)
   354  		if err != nil {
   355  			return false, err
   356  		}
   357  		if eni.Status == "InUse" {
   358  			instanceID = eni.InstanceId
   359  			return true, nil
   360  		}
   361  		return false, nil
   362  	})
   363  	if err != nil {
   364  		return "", err
   365  	}
   366  	return instanceID, nil
   367  }
   368  
   369  // DeleteNetworkInterface deletes an ENI with the specified ID
   370  func (c *Client) DeleteNetworkInterface(ctx context.Context, eniID string) error {
   371  	req := ecs.CreateDeleteNetworkInterfaceRequest()
   372  	req.NetworkInterfaceId = eniID
   373  	_, err := c.ecsClient.DeleteNetworkInterface(req)
   374  	if err != nil {
   375  		return err
   376  	}
   377  	return nil
   378  }
   379  
   380  // AssignPrivateIPAddresses assigns the specified number of secondary IP
   381  // return allocated IPs
   382  func (c *Client) AssignPrivateIPAddresses(ctx context.Context, eniID string, toAllocate int) ([]string, error) {
   383  	req := ecs.CreateAssignPrivateIpAddressesRequest()
   384  	req.NetworkInterfaceId = eniID
   385  	req.SecondaryPrivateIpAddressCount = requests.NewInteger(toAllocate)
   386  	resp, err := c.ecsClient.AssignPrivateIpAddresses(req)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  	return resp.AssignedPrivateIpAddressesSet.PrivateIpSet.PrivateIpAddress, nil
   391  }
   392  
   393  // PrUnassignivateIPAddresses unassign specified IP addresses from ENI
   394  // should not provide Primary IP
   395  func (c *Client) UnassignPrivateIPAddresses(ctx context.Context, eniID string, addresses []string) error {
   396  	req := ecs.CreateUnassignPrivateIpAddressesRequest()
   397  	req.NetworkInterfaceId = eniID
   398  	req.PrivateIpAddress = &addresses
   399  	_, err := c.ecsClient.UnassignPrivateIpAddresses(req)
   400  	return err
   401  }
   402  
   403  func (c *Client) describeNetworkInterfaces(ctx context.Context) ([]ecs.NetworkInterfaceSet, error) {
   404  	var result []ecs.NetworkInterfaceSet
   405  	req := ecs.CreateDescribeNetworkInterfacesRequest()
   406  	req.MaxResults = requests.NewInteger(500)
   407  
   408  	for {
   409  		c.limiter.Limit(ctx, "DescribeNetworkInterfaces")
   410  		resp, err := c.ecsClient.DescribeNetworkInterfaces(req)
   411  		if err != nil {
   412  			return nil, err
   413  		}
   414  
   415  		result = append(result, resp.NetworkInterfaceSets.NetworkInterfaceSet...)
   416  
   417  		if resp.NextToken == "" {
   418  			break
   419  		} else {
   420  			req.NextToken = resp.NextToken
   421  		}
   422  	}
   423  
   424  	return result, nil
   425  }
   426  
   427  func (c *Client) describeNetworkInterfacesByInstance(ctx context.Context, instanceID string) ([]ecs.NetworkInterfaceSet, error) {
   428  	var result []ecs.NetworkInterfaceSet
   429  
   430  	for i := 1; ; {
   431  		req := ecs.CreateDescribeNetworkInterfacesRequest()
   432  		req.PageNumber = requests.NewInteger(i)
   433  		req.PageSize = requests.NewInteger(1000)
   434  		req.InstanceId = instanceID
   435  		c.limiter.Limit(ctx, "DescribeNetworkInterfaces")
   436  		resp, err := c.ecsClient.DescribeNetworkInterfaces(req)
   437  		if err != nil {
   438  			return nil, err
   439  		}
   440  		if len(resp.NetworkInterfaceSets.NetworkInterfaceSet) == 0 {
   441  			break
   442  		}
   443  
   444  		result = append(result, resp.NetworkInterfaceSets.NetworkInterfaceSet...)
   445  
   446  		if resp.TotalCount < resp.PageNumber*resp.PageSize {
   447  			break
   448  		}
   449  		i++
   450  	}
   451  
   452  	return result, nil
   453  }
   454  
   455  // deriveStatus returns a status string based on the HTTP response provided by
   456  // the AlibabaCloud API server. If no specific status is provided, either "OK" or
   457  // "Failed" is returned based on the error variable.
   458  func deriveStatus(err error) string {
   459  	var respErr httperr.Error
   460  	if errors.As(err, &respErr) {
   461  		return respErr.ErrorCode()
   462  	}
   463  
   464  	if err != nil {
   465  		return "Failed"
   466  	}
   467  
   468  	return "OK"
   469  }
   470  
   471  // parseENI parses a ecs.NetworkInterface as returned by the ecs service API,
   472  // converts it into a eniTypes.ENI object
   473  func parseENI(iface *ecs.NetworkInterfaceSet, vpcs ipamTypes.VirtualNetworkMap, subnets ipamTypes.SubnetMap) (instanceID string, eni *eniTypes.ENI, err error) {
   474  	var privateIPSets []eniTypes.PrivateIPSet
   475  	for _, p := range iface.PrivateIpSets.PrivateIpSet {
   476  		privateIPSets = append(privateIPSets, eniTypes.PrivateIPSet{
   477  			Primary:          p.Primary,
   478  			PrivateIpAddress: p.PrivateIpAddress,
   479  		})
   480  	}
   481  
   482  	eni = &eniTypes.ENI{
   483  		NetworkInterfaceID: iface.NetworkInterfaceId,
   484  		MACAddress:         iface.MacAddress,
   485  		Type:               iface.Type,
   486  		InstanceID:         iface.InstanceId,
   487  		SecurityGroupIDs:   iface.SecurityGroupIds.SecurityGroupId,
   488  		VPC: eniTypes.VPC{
   489  			VPCID: iface.VpcId,
   490  		},
   491  		ZoneID: iface.ZoneId,
   492  		VSwitch: eniTypes.VSwitch{
   493  			VSwitchID: iface.VSwitchId,
   494  		},
   495  		PrimaryIPAddress: iface.PrivateIpAddress,
   496  		PrivateIPSets:    privateIPSets,
   497  		Tags:             parseECSTags(iface.Tags.Tag),
   498  	}
   499  	vpc, ok := vpcs[iface.VpcId]
   500  	if ok {
   501  		eni.VPC.CIDRBlock = vpc.PrimaryCIDR
   502  		eni.VPC.SecondaryCIDRs = vpc.CIDRs
   503  	}
   504  
   505  	subnet, ok := subnets[iface.VSwitchId]
   506  	if ok && subnet.CIDR != nil {
   507  		eni.VSwitch.CIDRBlock = subnet.CIDR.String()
   508  	}
   509  	return iface.InstanceId, eni, nil
   510  }
   511  
   512  // parseECSTags convert ECS Tags to ipam Tags
   513  func parseECSTags(tags []ecs.Tag) ipamTypes.Tags {
   514  	result := make(ipamTypes.Tags, len(tags))
   515  	for _, tag := range tags {
   516  		result[tag.TagKey] = tag.TagValue
   517  	}
   518  	return result
   519  }