yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/loadbalancer.go (about) 1 // Copyright 2019 Yunion 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 aws 16 17 import ( 18 "context" 19 "fmt" 20 "sort" 21 "strconv" 22 "strings" 23 24 "github.com/aws/aws-sdk-go/service/elbv2" 25 26 "yunion.io/x/jsonutils" 27 "yunion.io/x/log" 28 "yunion.io/x/pkg/errors" 29 "yunion.io/x/pkg/utils" 30 31 api "yunion.io/x/cloudmux/pkg/apis/compute" 32 "yunion.io/x/cloudmux/pkg/cloudprovider" 33 "yunion.io/x/cloudmux/pkg/multicloud" 34 ) 35 36 /* 37 https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/Welcome.html 38 */ 39 40 type SElb struct { 41 multicloud.SResourceBase 42 region *SRegion 43 44 Type string `json:"Type"` 45 Scheme string `json:"Scheme"` 46 IPAddressType string `json:"IpAddressType"` 47 VpcID string `json:"VpcId"` 48 AvailabilityZones []AvailabilityZone `json:"AvailabilityZones"` 49 CreatedTime string `json:"CreatedTime"` 50 CanonicalHostedZoneID string `json:"CanonicalHostedZoneId"` 51 DNSName string `json:"DNSName"` 52 SecurityGroups []string `json:"SecurityGroups"` 53 LoadBalancerName string `json:"LoadBalancerName"` 54 State State `json:"State"` 55 LoadBalancerArn string `json:"LoadBalancerArn"` 56 } 57 58 type AvailabilityZone struct { 59 LoadBalancerAddresses []LoadBalancerAddress `json:"LoadBalancerAddresses"` 60 ZoneName string `json:"ZoneName"` 61 SubnetID string `json:"SubnetId"` 62 } 63 64 type LoadBalancerAddress struct { 65 IPAddress string `json:"IpAddress"` 66 AllocationID string `json:"AllocationId"` 67 } 68 69 type State struct { 70 Code string `json:"Code"` 71 } 72 73 func (self *SElb) GetId() string { 74 return self.LoadBalancerArn 75 } 76 77 func (self *SElb) GetName() string { 78 return self.LoadBalancerName 79 } 80 81 func (self *SElb) GetGlobalId() string { 82 return self.GetId() 83 } 84 85 func (self *SElb) GetStatus() string { 86 switch self.State.Code { 87 case "provisioning": 88 return api.LB_STATUS_INIT 89 case "active": 90 return api.LB_STATUS_ENABLED 91 case "failed": 92 return api.LB_STATUS_START_FAILED 93 default: 94 return api.LB_STATUS_UNKNOWN 95 } 96 } 97 98 func (self *SElb) Refresh() error { 99 ielb, err := self.region.GetILoadBalancerById(self.GetId()) 100 if err != nil { 101 return err 102 } 103 104 err = jsonutils.Update(self, ielb) 105 if err != nil { 106 return err 107 } 108 109 return nil 110 } 111 112 func (self *SElb) IsEmulated() bool { 113 return false 114 } 115 116 func (self *SElb) GetSysTags() map[string]string { 117 data := map[string]string{} 118 data["loadbalance_type"] = self.Type 119 attrs, err := self.region.getElbAttributesById(self.GetId()) 120 if err != nil { 121 log.Errorf("SElb GetSysTags %s", err) 122 return data 123 } 124 125 for k, v := range attrs { 126 data[k] = v 127 } 128 return data 129 } 130 131 func (self *SElb) GetTags() (map[string]string, error) { 132 tags, err := self.region.FetchElbTags(self.LoadBalancerArn) 133 if err != nil { 134 return nil, errors.Wrap(err, "self.region.FetchElbTags") 135 } 136 return tags, nil 137 } 138 139 func (self *SElb) GetProjectId() string { 140 return "" 141 } 142 143 func (self *SElb) GetAddress() string { 144 return self.DNSName 145 } 146 147 func (self *SElb) GetAddressType() string { 148 switch self.Scheme { 149 case "internal": 150 return api.LB_ADDR_TYPE_INTRANET 151 case "internet-facing": 152 return api.LB_ADDR_TYPE_INTERNET 153 default: 154 return api.LB_ADDR_TYPE_INTRANET 155 } 156 } 157 158 func (self *SElb) GetNetworkType() string { 159 return api.LB_NETWORK_TYPE_VPC 160 } 161 162 func (self *SElb) GetNetworkIds() []string { 163 ret := []string{} 164 for i := range self.AvailabilityZones { 165 ret = append(ret, self.AvailabilityZones[i].SubnetID) 166 } 167 168 return ret 169 } 170 171 func (self *SElb) GetVpcId() string { 172 return self.VpcID 173 } 174 175 func (self *SElb) GetZoneId() string { 176 zones := []string{} 177 for i := range self.AvailabilityZones { 178 zones = append(zones, self.AvailabilityZones[i].ZoneName) 179 } 180 181 sort.Strings(zones) 182 if len(zones) > 0 { 183 z, err := self.region.getZoneById(zones[0]) 184 if err != nil { 185 log.Infof("getZoneById %s %s", zones[0], err) 186 return "" 187 } 188 189 return z.GetGlobalId() 190 } 191 192 return "" 193 } 194 195 func (self *SElb) GetZone1Id() string { 196 return "" 197 } 198 199 func (self *SElb) GetLoadbalancerSpec() string { 200 return self.Type 201 } 202 203 func (self *SElb) GetChargeType() string { 204 return api.LB_CHARGE_TYPE_BY_TRAFFIC 205 } 206 207 func (self *SElb) GetEgressMbps() int { 208 return 0 209 } 210 211 func (self *SElb) Delete(ctx context.Context) error { 212 return self.region.DeleteElb(self.GetId()) 213 } 214 215 func (self *SElb) Start() error { 216 return nil 217 } 218 219 func (self *SElb) Stop() error { 220 return cloudprovider.ErrNotSupported 221 } 222 223 func (self *SElb) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { 224 listeners, err := self.region.GetElbListeners(self.GetId()) 225 if err != nil { 226 return nil, errors.Wrap(err, "GetElbListeners") 227 } 228 229 ret := make([]cloudprovider.ICloudLoadbalancerListener, len(listeners)) 230 for i := range listeners { 231 listeners[i].lb = self 232 ret[i] = &listeners[i] 233 } 234 235 return ret, nil 236 } 237 238 func (self *SElb) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { 239 backendgroups, err := self.region.GetElbBackendgroups(self.GetId(), nil) 240 if err != nil { 241 return nil, errors.Wrap(err, "GetElbBackendgroups") 242 } 243 244 ibackendgroups := make([]cloudprovider.ICloudLoadbalancerBackendGroup, len(backendgroups)) 245 for i := range backendgroups { 246 backendgroups[i].lb = self 247 ibackendgroups[i] = &backendgroups[i] 248 } 249 250 return ibackendgroups, nil 251 } 252 253 func (self *SElb) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { 254 backendgroup, err := self.region.CreateElbBackendgroup(group) 255 if err != nil { 256 return nil, errors.Wrap(err, "CreateElbBackendgroup") 257 } 258 259 backendgroup.lb = self 260 return backendgroup, nil 261 } 262 263 func (self *SElb) GetILoadBalancerBackendGroupById(groupId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { 264 return self.region.GetElbBackendgroup(groupId) 265 } 266 267 func (self *SElb) CreateILoadBalancerListener(ctx context.Context, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { 268 ret, err := self.region.CreateElbListener(listener) 269 if err != nil { 270 return nil, errors.Wrap(err, "CreateElbListener") 271 } 272 273 ret.lb = self 274 return ret, nil 275 } 276 277 func (self *SElb) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { 278 if listenerId == "" { 279 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetILoadBalancerListenerById") 280 } 281 282 return self.region.GetElbListener(listenerId) 283 } 284 285 func (self *SElb) GetIEIP() (cloudprovider.ICloudEIP, error) { 286 return nil, nil 287 } 288 289 func (self *SRegion) DeleteElb(elbId string) error { 290 client, err := self.GetElbV2Client() 291 if err != nil { 292 return errors.Wrap(err, "GetElbV2Client") 293 } 294 295 params := &elbv2.DeleteLoadBalancerInput{} 296 params.SetLoadBalancerArn(elbId) 297 _, err = client.DeleteLoadBalancer(params) 298 if err != nil { 299 return errors.Wrap(err, "DeleteLoadBalancer") 300 } 301 302 return nil 303 } 304 305 func (self *SRegion) GetElbBackendgroups(elbId string, backendgroupIds []string) ([]SElbBackendGroup, error) { 306 client, err := self.GetElbV2Client() 307 if err != nil { 308 return nil, errors.Wrap(err, "GetElbV2Client") 309 } 310 311 params := &elbv2.DescribeTargetGroupsInput{} 312 if len(elbId) > 0 { 313 params.SetLoadBalancerArn(elbId) 314 } 315 316 if len(backendgroupIds) > 0 { 317 v := make([]*string, len(backendgroupIds)) 318 for i := range backendgroupIds { 319 v[i] = &backendgroupIds[i] 320 } 321 322 params.SetTargetGroupArns(v) 323 } 324 325 ret, err := client.DescribeTargetGroups(params) 326 if err != nil { 327 return nil, errors.Wrap(err, "DescribeTargetGroups") 328 } 329 330 backendgroups := []SElbBackendGroup{} 331 err = unmarshalAwsOutput(ret, "TargetGroups", &backendgroups) 332 if err != nil { 333 return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetGroups") 334 } 335 336 for i := range backendgroups { 337 backendgroups[i].region = self 338 } 339 340 return backendgroups, nil 341 } 342 343 func (self *SRegion) GetElbBackendgroup(backendgroupId string) (*SElbBackendGroup, error) { 344 client, err := self.GetElbV2Client() 345 if err != nil { 346 return nil, errors.Wrap(err, "GetElbV2Client") 347 } 348 349 params := &elbv2.DescribeTargetGroupsInput{} 350 params.SetTargetGroupArns([]*string{&backendgroupId}) 351 352 ret, err := client.DescribeTargetGroups(params) 353 if err != nil { 354 if strings.Contains(err.Error(), "TargetGroupNotFound") { 355 return nil, cloudprovider.ErrNotFound 356 } 357 return nil, errors.Wrap(err, "DescribeTargetGroups") 358 } 359 360 backendgroups := []SElbBackendGroup{} 361 err = unmarshalAwsOutput(ret, "TargetGroups", &backendgroups) 362 if err != nil { 363 return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetGroups") 364 } 365 366 if len(backendgroups) == 1 { 367 backendgroups[0].region = self 368 return &backendgroups[0], nil 369 } 370 371 return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetElbBackendgroup") 372 } 373 374 func ToAwsHealthCode(s string) string { 375 ret := []string{} 376 377 segs := strings.Split(s, ",") 378 for _, seg := range segs { 379 if seg == api.LB_HEALTH_CHECK_HTTP_CODE_4xx && !utils.IsInStringArray("400-499", ret) { 380 ret = append(ret, "400-499") 381 } else if seg == api.LB_HEALTH_CHECK_HTTP_CODE_3xx && !utils.IsInStringArray("300-399", ret) { 382 ret = append(ret, "300-399") 383 } else if seg == api.LB_HEALTH_CHECK_HTTP_CODE_2xx && !utils.IsInStringArray("200-299", ret) { 384 ret = append(ret, "200-299") 385 } 386 } 387 388 return strings.Join(ret, ",") 389 } 390 391 func ToOnecloudHealthCode(s string) string { 392 ret := []string{} 393 394 segs := strings.Split(s, ",") 395 for _, seg := range segs { 396 codes := strings.Split(seg, "-") 397 for _, code := range codes { 398 c, _ := strconv.Atoi(code) 399 if c >= 400 && !utils.IsInStringArray(api.LB_HEALTH_CHECK_HTTP_CODE_4xx, ret) { 400 ret = append(ret, api.LB_HEALTH_CHECK_HTTP_CODE_4xx) 401 } else if c >= 300 && !utils.IsInStringArray(api.LB_HEALTH_CHECK_HTTP_CODE_3xx, ret) { 402 ret = append(ret, api.LB_HEALTH_CHECK_HTTP_CODE_3xx) 403 } else if c >= 200 && !utils.IsInStringArray(api.LB_HEALTH_CHECK_HTTP_CODE_2xx, ret) { 404 ret = append(ret, api.LB_HEALTH_CHECK_HTTP_CODE_2xx) 405 } 406 } 407 408 if len(codes) == 2 { 409 min, _ := strconv.Atoi(codes[0]) 410 max, _ := strconv.Atoi(codes[1]) 411 412 if min >= 200 && max >= 400 { 413 if !utils.IsInStringArray(api.LB_HEALTH_CHECK_HTTP_CODE_3xx, ret) { 414 ret = append(ret, api.LB_HEALTH_CHECK_HTTP_CODE_3xx) 415 } 416 } 417 } 418 } 419 420 return strings.Join(ret, ",") 421 } 422 423 // 目前只支持target type :instance 424 func (self *SRegion) CreateElbBackendgroup(group *cloudprovider.SLoadbalancerBackendGroup) (*SElbBackendGroup, error) { 425 params := &elbv2.CreateTargetGroupInput{} 426 params.SetProtocol(strings.ToUpper(group.ListenType)) 427 params.SetPort(int64(group.ListenPort)) 428 params.SetVpcId(group.VpcId) 429 params.SetName(group.Name) 430 params.SetTargetType("instance") 431 if group.HealthCheck != nil { 432 params.SetHealthCheckPort("traffic-port") 433 params.SetHealthCheckProtocol(strings.ToUpper(group.HealthCheck.HealthCheckType)) 434 params.SetHealthCheckIntervalSeconds(int64(group.HealthCheck.HealthCheckInterval)) 435 params.SetHealthyThresholdCount(int64(group.HealthCheck.HealthCheckRise)) 436 437 if len(group.HealthCheck.HealthCheckURI) > 0 { 438 params.SetHealthCheckPath(group.HealthCheck.HealthCheckURI) 439 } 440 441 if utils.IsInStringArray(group.ListenType, []string{api.LB_HEALTH_CHECK_HTTP, api.LB_HEALTH_CHECK_HTTPS}) { 442 params.SetHealthCheckTimeoutSeconds(int64(group.HealthCheck.HealthCheckTimeout)) 443 params.SetUnhealthyThresholdCount(int64(group.HealthCheck.HealthCheckFail)) 444 445 codes := ToAwsHealthCode(group.HealthCheck.HealthCheckHttpCode) 446 if len(codes) > 0 { 447 matcher := &elbv2.Matcher{} 448 matcher.SetHttpCode(codes) 449 params.SetMatcher(matcher) 450 } 451 } else { 452 // tcp & udp 健康检查阈值与不健康阈值需相同 453 params.SetUnhealthyThresholdCount(int64(group.HealthCheck.HealthCheckRise)) 454 } 455 } 456 457 client, err := self.GetElbV2Client() 458 if err != nil { 459 return nil, errors.Wrap(err, "GetElbV2Client") 460 } 461 462 ret, err := client.CreateTargetGroup(params) 463 if err != nil { 464 return nil, errors.Wrap(err, "CreateTargetGroup") 465 } 466 467 backendgroups := []SElbBackendGroup{} 468 err = unmarshalAwsOutput(ret, "TargetGroups", &backendgroups) 469 if err != nil { 470 return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetGroups") 471 } 472 473 if len(backendgroups) == 1 { 474 backendgroups[0].region = self 475 return &backendgroups[0], nil 476 } 477 478 return nil, fmt.Errorf("CreateElbBackendgroup error: %#v", backendgroups) 479 } 480 481 func (self *SElb) SetTags(tags map[string]string, replace bool) error { 482 oldTags, err := self.region.FetchElbTags(self.LoadBalancerArn) 483 if err != nil { 484 return errors.Wrapf(err, "self.region.FetchElbTags(%s)", self.LoadBalancerArn) 485 } 486 err = self.region.UpdateResourceTags(self.LoadBalancerArn, oldTags, tags, replace) 487 if err != nil { 488 return errors.Wrap(err, "self.region.UpdateResourceTags(self.LoadBalancerArn, oldTags, tags, replace)") 489 } 490 return nil 491 } 492 493 func (self *SRegion) FetchElbTags(arn string) (map[string]string, error) { 494 client, err := self.GetElbV2Client() 495 if err != nil { 496 return nil, errors.Wrap(err, "GetElbV2Client") 497 } 498 params := elbv2.DescribeTagsInput{} 499 params.SetResourceArns([]*string{&arn}) 500 output, err := client.DescribeTags(¶ms) 501 if err != nil { 502 return nil, errors.Wrapf(err, "client.DescribeTags(%s)", jsonutils.Marshal(params).String()) 503 } 504 result := map[string]string{} 505 for i := range output.TagDescriptions { 506 if output.TagDescriptions[i].ResourceArn != nil && *output.TagDescriptions[i].ResourceArn == arn { 507 for j := range output.TagDescriptions[i].Tags { 508 if output.TagDescriptions[i].Tags[j].Key != nil && output.TagDescriptions[i].Tags[j].Value != nil { 509 result[*output.TagDescriptions[i].Tags[j].Key] = *output.TagDescriptions[i].Tags[j].Value 510 } 511 } 512 return result, nil 513 } 514 } 515 return nil, cloudprovider.ErrNotFound 516 }