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(&params)
   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  }