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 }