github.phpd.cn/cilium/cilium@v1.6.12/pkg/aws/ec2/ec2.go (about)

     1  // Copyright 2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package ec2
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"time"
    21  
    22  	"github.com/cilium/cilium/pkg/aws/types"
    23  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    24  	"github.com/cilium/cilium/pkg/spanstat"
    25  
    26  	"github.com/aws/aws-sdk-go-v2/aws"
    27  	"github.com/aws/aws-sdk-go-v2/service/ec2"
    28  	"golang.org/x/time/rate"
    29  )
    30  
    31  // Client represents an EC2 API client
    32  type Client struct {
    33  	ec2Client  *ec2.EC2
    34  	limiter    *rate.Limiter
    35  	metricsAPI metricsAPI
    36  }
    37  
    38  type metricsAPI interface {
    39  	ObserveEC2APICall(call, status string, duration float64)
    40  	ObserveEC2RateLimit(operation string, duration time.Duration)
    41  }
    42  
    43  // NewClient returns a new EC2 client
    44  func NewClient(ec2Client *ec2.EC2, metrics metricsAPI, rateLimit float64, burst int) *Client {
    45  	return &Client{
    46  		ec2Client:  ec2Client,
    47  		metricsAPI: metrics,
    48  		limiter:    rate.NewLimiter(rate.Limit(rateLimit), burst),
    49  	}
    50  }
    51  
    52  // deriveStatus returns a status string based on the HTTP response provided by
    53  // the AWS API server. If no specific status is provided, either "OK" or
    54  // "Failed" is returned based on the error variable.
    55  func deriveStatus(req *aws.Request, err error) string {
    56  	if req.HTTPResponse != nil {
    57  		return req.HTTPResponse.Status
    58  	}
    59  
    60  	if err != nil {
    61  		return "Failed"
    62  	}
    63  
    64  	return "OK"
    65  }
    66  
    67  func (c *Client) rateLimit(operation string) {
    68  	r := c.limiter.Reserve()
    69  	if delay := r.Delay(); delay != time.Duration(0) && delay != rate.InfDuration {
    70  		c.metricsAPI.ObserveEC2RateLimit(operation, delay)
    71  		c.limiter.Wait(context.TODO())
    72  	}
    73  }
    74  
    75  // describeNetworkInterfaces lists all ENIs
    76  func (c *Client) describeNetworkInterfaces() ([]ec2.NetworkInterface, error) {
    77  	var (
    78  		networkInterfaces []ec2.NetworkInterface
    79  		nextToken         string
    80  	)
    81  
    82  	for {
    83  		c.rateLimit("DescribeNetworkInterfaces")
    84  		req := &ec2.DescribeNetworkInterfacesInput{}
    85  		if nextToken != "" {
    86  			req.NextToken = &nextToken
    87  		}
    88  
    89  		sinceStart := spanstat.Start()
    90  		listReq := c.ec2Client.DescribeNetworkInterfacesRequest(req)
    91  		response, err := listReq.Send()
    92  		c.metricsAPI.ObserveEC2APICall("DescribeNetworkInterfaces", deriveStatus(listReq.Request, err), sinceStart.Seconds())
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  
    97  		networkInterfaces = append(networkInterfaces, response.NetworkInterfaces...)
    98  
    99  		if response.NextToken == nil || *response.NextToken == "" {
   100  			break
   101  		} else {
   102  			nextToken = *response.NextToken
   103  		}
   104  	}
   105  
   106  	return networkInterfaces, nil
   107  }
   108  
   109  // parseENI parses a ec2.NetworkInterface as returned by the EC2 service API,
   110  // converts it into a v2. ENI object
   111  func parseENI(iface *ec2.NetworkInterface, vpcs types.VpcMap, subnets types.SubnetMap) (instanceID string, eni *v2.ENI, err error) {
   112  	if iface.PrivateIpAddress == nil {
   113  		err = fmt.Errorf("ENI has no IP address")
   114  		return
   115  	}
   116  
   117  	eni = &v2.ENI{
   118  		IP:             *iface.PrivateIpAddress,
   119  		SecurityGroups: []string{},
   120  		Addresses:      []string{},
   121  	}
   122  
   123  	if iface.MacAddress != nil {
   124  		eni.MAC = *iface.MacAddress
   125  	}
   126  
   127  	if iface.NetworkInterfaceId != nil {
   128  		eni.ID = *iface.NetworkInterfaceId
   129  	}
   130  
   131  	if iface.Description != nil {
   132  		eni.Description = *iface.Description
   133  	}
   134  
   135  	if iface.Attachment != nil {
   136  		if iface.Attachment.DeviceIndex != nil {
   137  			eni.Number = int(*iface.Attachment.DeviceIndex)
   138  		}
   139  
   140  		if iface.Attachment.InstanceId != nil {
   141  			instanceID = *iface.Attachment.InstanceId
   142  		}
   143  	}
   144  
   145  	if iface.SubnetId != nil {
   146  		eni.Subnet.ID = *iface.SubnetId
   147  
   148  		if subnets != nil {
   149  			if subnet, ok := subnets[eni.Subnet.ID]; ok {
   150  				eni.Subnet.CIDR = subnet.CIDR
   151  			}
   152  		}
   153  	}
   154  
   155  	if iface.VpcId != nil {
   156  		eni.VPC.ID = *iface.VpcId
   157  
   158  		if vpcs != nil {
   159  			if vpc, ok := vpcs[eni.VPC.ID]; ok {
   160  				eni.VPC.PrimaryCIDR = vpc.PrimaryCIDR
   161  			}
   162  		}
   163  	}
   164  
   165  	for _, ip := range iface.PrivateIpAddresses {
   166  		if ip.PrivateIpAddress != nil {
   167  			eni.Addresses = append(eni.Addresses, *ip.PrivateIpAddress)
   168  		}
   169  	}
   170  
   171  	for _, g := range iface.Groups {
   172  		if g.GroupId != nil {
   173  			eni.SecurityGroups = append(eni.SecurityGroups, *g.GroupId)
   174  		}
   175  	}
   176  
   177  	return
   178  }
   179  
   180  // GetInstances returns the list of all instances including their ENIs as
   181  // instanceMap
   182  func (c *Client) GetInstances(vpcs types.VpcMap, subnets types.SubnetMap) (types.InstanceMap, error) {
   183  	instances := types.InstanceMap{}
   184  
   185  	networkInterfaces, err := c.describeNetworkInterfaces()
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	for _, iface := range networkInterfaces {
   191  		id, eni, err := parseENI(&iface, vpcs, subnets)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  
   196  		if id != "" {
   197  			instances.Add(id, eni)
   198  		}
   199  	}
   200  
   201  	return instances, nil
   202  }
   203  
   204  // describeVpcs lists all VPCs
   205  func (c *Client) describeVpcs() ([]ec2.Vpc, error) {
   206  	var vpcs []ec2.Vpc
   207  
   208  	c.rateLimit("DescribeVpcs")
   209  	req := &ec2.DescribeVpcsInput{}
   210  
   211  	sinceStart := spanstat.Start()
   212  	listReq := c.ec2Client.DescribeVpcsRequest(req)
   213  	response, err := listReq.Send()
   214  	c.metricsAPI.ObserveEC2APICall("DescribeVpcs", deriveStatus(listReq.Request, err), sinceStart.Seconds())
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	vpcs = append(vpcs, response.Vpcs...)
   220  
   221  	return vpcs, nil
   222  }
   223  
   224  // GetVpcs retrieves and returns all Vpcs
   225  func (c *Client) GetVpcs() (types.VpcMap, error) {
   226  	vpcs := types.VpcMap{}
   227  
   228  	vpcList, err := c.describeVpcs()
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	for _, v := range vpcList {
   234  		vpc := &types.Vpc{ID: *v.VpcId}
   235  
   236  		if v.CidrBlock != nil {
   237  			vpc.PrimaryCIDR = *v.CidrBlock
   238  		}
   239  
   240  		vpcs[vpc.ID] = vpc
   241  	}
   242  
   243  	return vpcs, nil
   244  }
   245  
   246  // describeSubnets lists all subnets
   247  func (c *Client) describeSubnets() ([]ec2.Subnet, error) {
   248  	sinceStart := spanstat.Start()
   249  	listReq := c.ec2Client.DescribeSubnetsRequest(&ec2.DescribeSubnetsInput{})
   250  	result, err := listReq.Send()
   251  	c.metricsAPI.ObserveEC2APICall("DescribeSubnets", deriveStatus(listReq.Request, err), sinceStart.Seconds())
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	return result.Subnets, nil
   257  }
   258  
   259  // GetSubnets returns all EC2 subnets as a subnetMap
   260  func (c *Client) GetSubnets() (types.SubnetMap, error) {
   261  	subnets := types.SubnetMap{}
   262  
   263  	subnetList, err := c.describeSubnets()
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	for _, s := range subnetList {
   269  		subnet := &types.Subnet{
   270  			ID:                 *s.SubnetId,
   271  			CIDR:               *s.CidrBlock,
   272  			AvailableAddresses: int(*s.AvailableIpAddressCount),
   273  			Tags:               map[string]string{},
   274  		}
   275  
   276  		if s.AvailabilityZone != nil {
   277  			subnet.AvailabilityZone = *s.AvailabilityZone
   278  		}
   279  
   280  		if s.VpcId != nil {
   281  			subnet.VpcID = *s.VpcId
   282  		}
   283  
   284  		for _, tag := range s.Tags {
   285  			if *tag.Key == "Name" {
   286  				subnet.Name = *tag.Value
   287  			}
   288  			subnet.Tags[*tag.Key] = *tag.Value
   289  		}
   290  
   291  		subnets[subnet.ID] = subnet
   292  	}
   293  
   294  	return subnets, nil
   295  }
   296  
   297  // CreateNetworkInterface creates an ENI with the given parameters
   298  func (c *Client) CreateNetworkInterface(toAllocate int64, subnetID, desc string, groups []string) (string, *v2.ENI, error) {
   299  	createReq := &ec2.CreateNetworkInterfaceInput{
   300  		Description:                    &desc,
   301  		SecondaryPrivateIpAddressCount: &toAllocate,
   302  		SubnetId:                       &subnetID,
   303  	}
   304  	for _, grp := range groups {
   305  		createReq.Groups = append(createReq.Groups, grp)
   306  	}
   307  
   308  	c.rateLimit("CreateNetworkInterface")
   309  	sinceStart := spanstat.Start()
   310  	create := c.ec2Client.CreateNetworkInterfaceRequest(createReq)
   311  	resp, err := create.Send()
   312  	c.metricsAPI.ObserveEC2APICall("CreateNetworkInterfaceRequest", deriveStatus(create.Request, err), sinceStart.Seconds())
   313  	if err != nil {
   314  		return "", nil, err
   315  	}
   316  
   317  	_, eni, err := parseENI(resp.NetworkInterface, nil, nil)
   318  	if err != nil {
   319  		// The error is ignored on purpose. The allocation itself has
   320  		// succeeded. The ability to parse and return the ENI
   321  		// information is optional. Returning the ENI ID is sufficient
   322  		// to allow for the caller to retrieve the ENI information via
   323  		// the API or wait for a regular sync to fetch the information.
   324  		return *resp.NetworkInterface.NetworkInterfaceId, nil, nil
   325  	}
   326  
   327  	return eni.ID, eni, nil
   328  
   329  }
   330  
   331  // DeleteNetworkInterface deletes an ENI with the specified ID
   332  func (c *Client) DeleteNetworkInterface(eniID string) error {
   333  	delReq := &ec2.DeleteNetworkInterfaceInput{}
   334  	delReq.NetworkInterfaceId = &eniID
   335  
   336  	c.rateLimit("DeleteNetworkInterface")
   337  	sinceStart := spanstat.Start()
   338  	req := c.ec2Client.DeleteNetworkInterfaceRequest(delReq)
   339  	_, err := req.Send()
   340  	c.metricsAPI.ObserveEC2APICall("DeleteNetworkInterface", deriveStatus(req.Request, err), sinceStart.Seconds())
   341  	return err
   342  }
   343  
   344  // AttachNetworkInterface attaches a previously created ENI to an instance
   345  func (c *Client) AttachNetworkInterface(index int64, instanceID, eniID string) (string, error) {
   346  	attachReq := &ec2.AttachNetworkInterfaceInput{
   347  		DeviceIndex:        &index,
   348  		InstanceId:         &instanceID,
   349  		NetworkInterfaceId: &eniID,
   350  	}
   351  
   352  	c.rateLimit("AttachNetworkInterface")
   353  	sinceStart := spanstat.Start()
   354  	attach := c.ec2Client.AttachNetworkInterfaceRequest(attachReq)
   355  	attachResp, err := attach.Send()
   356  	c.metricsAPI.ObserveEC2APICall("AttachNetworkInterface", deriveStatus(attach.Request, err), sinceStart.Seconds())
   357  	if err != nil {
   358  		return "", err
   359  	}
   360  
   361  	return *attachResp.AttachmentId, nil
   362  }
   363  
   364  // ModifyNetworkInterface modifies the attributes of an ENI
   365  func (c *Client) ModifyNetworkInterface(eniID, attachmentID string, deleteOnTermination bool) error {
   366  	changes := &ec2.NetworkInterfaceAttachmentChanges{
   367  		AttachmentId:        &attachmentID,
   368  		DeleteOnTermination: &deleteOnTermination,
   369  	}
   370  
   371  	modifyReq := &ec2.ModifyNetworkInterfaceAttributeInput{
   372  		Attachment:         changes,
   373  		NetworkInterfaceId: &eniID,
   374  	}
   375  
   376  	c.rateLimit("ModifyNetworkInterfaceAttribute")
   377  	sinceStart := spanstat.Start()
   378  	modify := c.ec2Client.ModifyNetworkInterfaceAttributeRequest(modifyReq)
   379  	_, err := modify.Send()
   380  	c.metricsAPI.ObserveEC2APICall("ModifyNetworkInterface", deriveStatus(modify.Request, err), sinceStart.Seconds())
   381  	return err
   382  }
   383  
   384  // AssignPrivateIpAddresses assigns the specified number of secondary IP
   385  // addresses
   386  func (c *Client) AssignPrivateIpAddresses(eniID string, addresses int64) error {
   387  	request := ec2.AssignPrivateIpAddressesInput{
   388  		NetworkInterfaceId:             &eniID,
   389  		SecondaryPrivateIpAddressCount: &addresses,
   390  	}
   391  
   392  	c.rateLimit("AssignPrivateIpAddresses")
   393  	sinceStart := spanstat.Start()
   394  	req := c.ec2Client.AssignPrivateIpAddressesRequest(&request)
   395  	_, err := req.Send()
   396  	c.metricsAPI.ObserveEC2APICall("AssignPrivateIpAddresses", deriveStatus(req.Request, err), sinceStart.Seconds())
   397  	return err
   398  }
   399  
   400  // UnassignPrivateIpAddresses unassigns specified IP addresses from ENI
   401  func (c *Client) UnassignPrivateIpAddresses(eniID string, addresses []string) error {
   402  	request := ec2.UnassignPrivateIpAddressesInput{
   403  		NetworkInterfaceId: &eniID,
   404  		PrivateIpAddresses: addresses,
   405  	}
   406  
   407  	c.rateLimit("UnassignPrivateIpAddresses")
   408  	sinceStart := spanstat.Start()
   409  	req := c.ec2Client.UnassignPrivateIpAddressesRequest(&request)
   410  	_, err := req.Send()
   411  	c.metricsAPI.ObserveEC2APICall("UnassignPrivateIpAddresses", deriveStatus(req.Request, err), sinceStart.Seconds())
   412  	return err
   413  }
   414  
   415  // TagENI creates the specified tags on the ENI
   416  func (c *Client) TagENI(ctx context.Context, eniID string, eniTags map[string]string) error {
   417  	request := ec2.CreateTagsInput{
   418  		Resources: []string{eniID},
   419  		Tags:      createAWSTagSlice(eniTags),
   420  	}
   421  	c.rateLimit("CreateTags")
   422  	sinceStart := spanstat.Start()
   423  	req := c.ec2Client.CreateTagsRequest(&request)
   424  	_, err := req.Send()
   425  	c.metricsAPI.ObserveEC2APICall("CreateTags", deriveStatus(req.Request, err), sinceStart.Seconds())
   426  	return err
   427  }
   428  
   429  func createAWSTagSlice(tags map[string]string) []ec2.Tag {
   430  	awsTags := make([]ec2.Tag, 0, len(tags))
   431  	for k, v := range tags {
   432  		awsTag := ec2.Tag{
   433  			Key:   aws.String(k),
   434  			Value: aws.String(v),
   435  		}
   436  		awsTags = append(awsTags, awsTag)
   437  	}
   438  
   439  	return awsTags
   440  }