yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/loadbalancerbackendgroup.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  	"strconv"
    21  	"strings"
    22  
    23  	"github.com/aws/aws-sdk-go/service/elbv2"
    24  	"github.com/pkg/errors"
    25  
    26  	"yunion.io/x/jsonutils"
    27  	"yunion.io/x/log"
    28  	"yunion.io/x/pkg/utils"
    29  
    30  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    31  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    32  	"yunion.io/x/cloudmux/pkg/multicloud"
    33  )
    34  
    35  type SElbBackendGroup struct {
    36  	multicloud.SResourceBase
    37  	AwsTags
    38  	region *SRegion
    39  	lb     *SElb
    40  
    41  	TargetGroupName            string   `json:"TargetGroupName"`
    42  	Protocol                   string   `json:"Protocol"`
    43  	Port                       int64    `json:"Port"`
    44  	VpcID                      string   `json:"VpcId"`
    45  	TargetType                 string   `json:"TargetType"`
    46  	HealthyThresholdCount      int      `json:"HealthyThresholdCount"`
    47  	Matcher                    Matcher  `json:"Matcher"`
    48  	UnhealthyThresholdCount    int      `json:"UnhealthyThresholdCount"`
    49  	HealthCheckPath            string   `json:"HealthCheckPath"`
    50  	HealthCheckProtocol        string   `json:"HealthCheckProtocol"`
    51  	HealthCheckPort            string   `json:"HealthCheckPort"`
    52  	HealthCheckIntervalSeconds int      `json:"HealthCheckIntervalSeconds"`
    53  	HealthCheckTimeoutSeconds  int      `json:"HealthCheckTimeoutSeconds"`
    54  	TargetGroupArn             string   `json:"TargetGroupArn"`
    55  	LoadBalancerArns           []string `json:"LoadBalancerArns"`
    56  }
    57  
    58  func (self *SElbBackendGroup) GetLoadbalancerId() string {
    59  	if len(self.LoadBalancerArns) > 0 {
    60  		return self.LoadBalancerArns[0]
    61  	}
    62  
    63  	return ""
    64  }
    65  
    66  func (self *SElbBackendGroup) GetILoadbalancer() cloudprovider.ICloudLoadbalancer {
    67  	return self.lb
    68  }
    69  
    70  type Matcher struct {
    71  	HTTPCode string `json:"HttpCode"`
    72  }
    73  
    74  func (self *SElbBackendGroup) GetId() string {
    75  	return self.TargetGroupArn
    76  }
    77  
    78  func (self *SElbBackendGroup) GetName() string {
    79  	return self.TargetGroupName
    80  }
    81  
    82  func (self *SElbBackendGroup) GetGlobalId() string {
    83  	return self.GetId()
    84  }
    85  
    86  func (self *SElbBackendGroup) GetStatus() string {
    87  	return api.LB_STATUS_ENABLED
    88  }
    89  
    90  func (self *SElbBackendGroup) Refresh() error {
    91  	lbbg, err := self.region.GetElbBackendgroup(self.GetId())
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	err = jsonutils.Update(self, lbbg)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	return nil
   102  }
   103  
   104  func (self *SElbBackendGroup) IsEmulated() bool {
   105  	return false
   106  }
   107  
   108  func (self *SElbBackendGroup) GetSysTags() map[string]string {
   109  	data := map[string]string{}
   110  	data["port"] = strconv.FormatInt(self.Port, 10)
   111  	data["target_type"] = self.TargetType
   112  	data["health_check_protocol"] = strings.ToLower(self.HealthCheckProtocol)
   113  	data["health_check_interval"] = strconv.Itoa(self.HealthCheckIntervalSeconds)
   114  	return data
   115  }
   116  
   117  func (self *SElbBackendGroup) GetProjectId() string {
   118  	return ""
   119  }
   120  
   121  func (self *SElbBackendGroup) IsDefault() bool {
   122  	return false
   123  }
   124  
   125  func (self *SElbBackendGroup) GetType() string {
   126  	return api.LB_BACKENDGROUP_TYPE_NORMAL
   127  }
   128  
   129  func (self *SElbBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) {
   130  	backends, err := self.region.GetELbBackends(self.GetId())
   131  	if err != nil {
   132  		return nil, errors.Wrap(err, "GetELbBackends")
   133  	}
   134  
   135  	ibackends := make([]cloudprovider.ICloudLoadbalancerBackend, len(backends))
   136  	for i := range backends {
   137  		backends[i].region = self.region
   138  		backends[i].group = self
   139  		ibackends[i] = &backends[i]
   140  	}
   141  
   142  	return ibackends, nil
   143  }
   144  
   145  func (self *SElbBackendGroup) GetILoadbalancerBackendById(backendId string) (cloudprovider.ICloudLoadbalancerBackend, error) {
   146  	backend, err := self.region.GetELbBackend(backendId)
   147  	if err != nil {
   148  		return nil, errors.Wrap(err, "GetELbBackend")
   149  	}
   150  
   151  	backend.group = self
   152  	return backend, nil
   153  }
   154  
   155  func (self *SElbBackendGroup) GetProtocolType() string {
   156  	switch self.Protocol {
   157  	case "TCP":
   158  		return api.LB_LISTENER_TYPE_TCP
   159  	case "UDP":
   160  		return api.LB_LISTENER_TYPE_UDP
   161  	case "HTTP":
   162  		return api.LB_LISTENER_TYPE_HTTP
   163  	case "HTTPS":
   164  		return api.LB_LISTENER_TYPE_HTTPS
   165  	case "TCP_UDP":
   166  		return api.LB_LISTENER_TYPE_TCP_UDP
   167  	default:
   168  		return ""
   169  	}
   170  }
   171  
   172  func (self *SElbBackendGroup) GetScheduler() string {
   173  	return ""
   174  }
   175  
   176  func (self *SElbBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) {
   177  	health := &cloudprovider.SLoadbalancerHealthCheck{}
   178  	health.HealthCheck = api.LB_BOOL_ON
   179  	health.HealthCheckRise = self.HealthyThresholdCount
   180  	health.HealthCheckFail = self.UnhealthyThresholdCount
   181  	health.HealthCheckInterval = self.HealthCheckIntervalSeconds
   182  	health.HealthCheckURI = self.HealthCheckPath
   183  	health.HealthCheckType = strings.ToLower(self.HealthCheckProtocol)
   184  	health.HealthCheckTimeout = self.HealthCheckTimeoutSeconds
   185  	health.HealthCheckHttpCode = ToOnecloudHealthCode(self.Matcher.HTTPCode)
   186  	return health, nil
   187  }
   188  
   189  func (self *SElbBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) {
   190  	attrs, err := self.region.GetElbBackendgroupAttributesById(self.GetId())
   191  	if err != nil {
   192  		return nil, errors.Wrap(err, "GetElbBackendgroupAttributesById")
   193  	}
   194  
   195  	cookieTime := 0
   196  	if t, ok := attrs["stickiness.lb_cookie.duration_seconds"]; !ok {
   197  		cookieTime, err = strconv.Atoi(t)
   198  	}
   199  
   200  	ret := &cloudprovider.SLoadbalancerStickySession{
   201  		StickySession:              attrs["stickiness.enabled"],
   202  		StickySessionCookie:        "",
   203  		StickySessionType:          api.LB_STICKY_SESSION_TYPE_INSERT,
   204  		StickySessionCookieTimeout: cookieTime,
   205  	}
   206  
   207  	return ret, nil
   208  }
   209  
   210  func (self *SElbBackendGroup) AddBackendServer(serverId string, weight int, port int) (cloudprovider.ICloudLoadbalancerBackend, error) {
   211  	backend, err := self.region.AddElbBackend(self.GetId(), serverId, weight, port)
   212  	if err != nil {
   213  		return nil, errors.Wrap(err, "AddElbBackend")
   214  	}
   215  
   216  	backend.region = self.region
   217  	backend.group = self
   218  	return backend, nil
   219  }
   220  
   221  func (self *SElbBackendGroup) RemoveBackendServer(serverId string, weight int, port int) error {
   222  	return self.region.RemoveElbBackend(self.GetId(), serverId, weight, port)
   223  }
   224  
   225  func (self *SElbBackendGroup) Delete(ctx context.Context) error {
   226  	return self.region.DeleteElbBackendGroup(self.GetId())
   227  }
   228  
   229  func (self *SElbBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error {
   230  	return self.region.SyncELbBackendGroup(self.GetId(), group)
   231  }
   232  
   233  func (self *SRegion) GetELbBackends(backendgroupId string) ([]SElbBackend, error) {
   234  	client, err := self.GetElbV2Client()
   235  	if err != nil {
   236  		return nil, errors.Wrap(err, "GetElbV2Client")
   237  	}
   238  
   239  	group, err := self.GetElbBackendgroup(backendgroupId)
   240  	if err != nil {
   241  		return nil, errors.Wrap(err, "GetElbBackendgroup")
   242  	}
   243  
   244  	params := &elbv2.DescribeTargetHealthInput{}
   245  	params.SetTargetGroupArn(backendgroupId)
   246  	output, err := client.DescribeTargetHealth(params)
   247  	if err != nil {
   248  		return nil, errors.Wrap(err, "DescribeTargetHealth")
   249  	}
   250  
   251  	backends := []SElbBackend{}
   252  	err = unmarshalAwsOutput(output, "TargetHealthDescriptions", &backends)
   253  	if err != nil {
   254  		return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetHealthDescriptions")
   255  	}
   256  
   257  	ret := []SElbBackend{}
   258  	for i := range backends {
   259  		if !utils.IsInStringArray(backends[i].TargetHealth.Reason, []string{"Target.InvalidState", "Target.NotInUse", "Target.DeregistrationInProgress"}) {
   260  			backends[i].region = self
   261  			backends[i].group = group
   262  			ret = append(ret, backends[i])
   263  		}
   264  	}
   265  
   266  	return ret, nil
   267  }
   268  
   269  func (self *SRegion) GetELbBackend(backendId string) (*SElbBackend, error) {
   270  	client, err := self.GetElbV2Client()
   271  	if err != nil {
   272  		return nil, errors.Wrap(err, "GetElbV2Client")
   273  	}
   274  
   275  	groupId, instanceId, port, err := parseElbBackendId(backendId)
   276  	if err != nil {
   277  		log.Errorf("parseElbBackendId %s: %s", backendId, err)
   278  		return nil, errors.Wrap(err, "parseElbBackendId")
   279  	}
   280  
   281  	params := &elbv2.DescribeTargetHealthInput{}
   282  	desc := &elbv2.TargetDescription{}
   283  	desc.SetPort(int64(port))
   284  	desc.SetId(instanceId)
   285  	params.SetTargets([]*elbv2.TargetDescription{desc})
   286  	params.SetTargetGroupArn(groupId)
   287  	ret, err := client.DescribeTargetHealth(params)
   288  	if err != nil {
   289  		return nil, errors.Wrap(err, "DescribeTargetHealth")
   290  	}
   291  
   292  	backends := []SElbBackend{}
   293  	err = unmarshalAwsOutput(ret, "TargetHealthDescriptions", &backends)
   294  	if err != nil {
   295  		return nil, errors.Wrap(err, "unmarshalAwsOutput.TargetHealthDescriptions")
   296  	}
   297  
   298  	if len(backends) == 1 {
   299  		backends[0].region = self
   300  		return &backends[0], nil
   301  	}
   302  
   303  	return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetELbBackend")
   304  }
   305  
   306  func parseElbBackendId(id string) (string, string, int, error) {
   307  	segs := strings.Split(id, "::")
   308  	if len(segs) != 3 {
   309  		return "", "", 0, fmt.Errorf("%s is not a valid backend id", id)
   310  	}
   311  
   312  	port, err := strconv.Atoi(segs[2])
   313  	if err != nil {
   314  		return "", "", 0, fmt.Errorf("%s is not a valid backend id, %s", id, err)
   315  	}
   316  
   317  	return segs[0], segs[1], port, nil
   318  }
   319  
   320  func genElbBackendId(backendgroupId string, serverId string, port int) string {
   321  	return strings.Join([]string{backendgroupId, serverId, strconv.Itoa(port)}, "::")
   322  }
   323  
   324  func (self *SRegion) AddElbBackend(backendgroupId, serverId string, weight int, port int) (*SElbBackend, error) {
   325  	client, err := self.GetElbV2Client()
   326  	if err != nil {
   327  		return nil, errors.Wrap(err, "GetElbV2Client")
   328  	}
   329  
   330  	params := &elbv2.RegisterTargetsInput{}
   331  	params.SetTargetGroupArn(backendgroupId)
   332  	desc := &elbv2.TargetDescription{}
   333  	desc.SetId(serverId)
   334  	desc.SetPort(int64(port))
   335  	params.SetTargets([]*elbv2.TargetDescription{desc})
   336  	_, err = client.RegisterTargets(params)
   337  	if err != nil {
   338  		return nil, errors.Wrap(err, "RegisterTargets")
   339  	}
   340  
   341  	return self.GetELbBackend(genElbBackendId(backendgroupId, serverId, port))
   342  }
   343  
   344  func (self *SRegion) RemoveElbBackend(backendgroupId, serverId string, weight int, port int) error {
   345  	client, err := self.GetElbV2Client()
   346  	if err != nil {
   347  		return errors.Wrap(err, "GetElbV2Client")
   348  	}
   349  
   350  	params := &elbv2.DeregisterTargetsInput{}
   351  	params.SetTargetGroupArn(backendgroupId)
   352  	desc := &elbv2.TargetDescription{}
   353  	desc.SetId(serverId)
   354  	desc.SetPort(int64(port))
   355  	params.SetTargets([]*elbv2.TargetDescription{desc})
   356  	_, err = client.DeregisterTargets(params)
   357  	if err != nil {
   358  		return errors.Wrap(err, "DeregisterTargets")
   359  	}
   360  
   361  	return nil
   362  }
   363  
   364  func (self *SRegion) DeleteElbBackendGroup(backendgroupId string) error {
   365  	client, err := self.GetElbV2Client()
   366  	if err != nil {
   367  		return errors.Wrap(err, "GetElbV2Client")
   368  	}
   369  
   370  	params := &elbv2.DeleteTargetGroupInput{}
   371  	params.SetTargetGroupArn(backendgroupId)
   372  	_, err = client.DeleteTargetGroup(params)
   373  	if err != nil {
   374  		return errors.Wrap(err, "DeleteTargetGroup")
   375  	}
   376  
   377  	return nil
   378  }
   379  
   380  func (self *SRegion) SyncELbBackendGroup(backendgroupId string, group *cloudprovider.SLoadbalancerBackendGroup) error {
   381  	err := self.modifyELbBackendGroup(backendgroupId, group.HealthCheck)
   382  	if err != nil {
   383  		return errors.Wrap(err, "modifyELbBackendGroup")
   384  	}
   385  
   386  	err = self.RemoveElbBackends(backendgroupId)
   387  	if err != nil {
   388  		log.Errorf("RemoveElbBackends %s: %s", backendgroupId, err)
   389  		return errors.Wrap(err, "RemoveElbBackends")
   390  	}
   391  
   392  	return self.AddElbBackends(backendgroupId, group.Backends)
   393  }
   394  
   395  func (self *SRegion) modifyELbBackendGroup(backendgroupId string, healthCheck *cloudprovider.SLoadbalancerHealthCheck) error {
   396  	client, err := self.GetElbV2Client()
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	params := &elbv2.ModifyTargetGroupInput{}
   402  	params.SetTargetGroupArn(backendgroupId)
   403  	params.SetHealthCheckProtocol(strings.ToUpper(healthCheck.HealthCheckType))
   404  	params.SetHealthyThresholdCount(int64(healthCheck.HealthCheckRise))
   405  
   406  	if utils.IsInStringArray(healthCheck.HealthCheckType, []string{api.LB_HEALTH_CHECK_HTTP, api.LB_LISTENER_TYPE_HTTPS}) {
   407  		params.SetUnhealthyThresholdCount(int64(healthCheck.HealthCheckFail))
   408  		params.SetHealthCheckTimeoutSeconds(int64(healthCheck.HealthCheckTimeout))
   409  		params.SetHealthCheckIntervalSeconds(int64(healthCheck.HealthCheckInterval))
   410  		if len(healthCheck.HealthCheckURI) > 0 {
   411  			params.SetHealthCheckPath(healthCheck.HealthCheckURI)
   412  		}
   413  
   414  		codes := ToAwsHealthCode(healthCheck.HealthCheckHttpCode)
   415  		if len(codes) > 0 {
   416  			matcher := &elbv2.Matcher{}
   417  			matcher.SetHttpCode(codes)
   418  			params.SetMatcher(matcher)
   419  		}
   420  	}
   421  
   422  	_, err = client.ModifyTargetGroup(params)
   423  	if err != nil {
   424  		return errors.Wrap(err, "ModifyTargetGroup")
   425  	}
   426  
   427  	return nil
   428  }
   429  
   430  func (self *SRegion) RemoveElbBackends(backendgroupId string) error {
   431  	client, err := self.GetElbV2Client()
   432  	if err != nil {
   433  		return err
   434  	}
   435  
   436  	backends, err := self.GetELbBackends(backendgroupId)
   437  	if err != nil {
   438  		return errors.Wrap(err, "GetELbBackends")
   439  	}
   440  
   441  	if len(backends) == 0 {
   442  		return nil
   443  	}
   444  
   445  	targets := []*elbv2.TargetDescription{}
   446  	for i := range backends {
   447  		target := &elbv2.TargetDescription{}
   448  		target.SetId(backends[i].GetBackendId())
   449  		target.SetPort(int64(backends[i].GetPort()))
   450  		targets = append(targets, target)
   451  	}
   452  
   453  	params := &elbv2.DeregisterTargetsInput{}
   454  	params.SetTargetGroupArn(backendgroupId)
   455  	params.SetTargets(targets)
   456  	_, err = client.DeregisterTargets(params)
   457  	if err != nil {
   458  		return errors.Wrap(err, "DeregisterTargets")
   459  	}
   460  
   461  	return nil
   462  }
   463  
   464  func (self *SRegion) AddElbBackends(backendgroupId string, backends []cloudprovider.SLoadbalancerBackend) error {
   465  	client, err := self.GetElbV2Client()
   466  	if err != nil {
   467  		return err
   468  	}
   469  
   470  	if len(backends) == 0 {
   471  		return nil
   472  	}
   473  
   474  	params := &elbv2.RegisterTargetsInput{}
   475  	params.SetTargetGroupArn(backendgroupId)
   476  	targets := []*elbv2.TargetDescription{}
   477  	for i := range backends {
   478  		desc := &elbv2.TargetDescription{}
   479  		desc.SetId(backends[i].ExternalID)
   480  		desc.SetPort(int64(backends[i].Port))
   481  		targets = append(targets, desc)
   482  	}
   483  
   484  	params.SetTargets(targets)
   485  	_, err = client.RegisterTargets(params)
   486  	if err != nil {
   487  		return errors.Wrap(err, "RegisterTargets")
   488  	}
   489  
   490  	return nil
   491  }
   492  
   493  func (self *SRegion) GetElbBackendgroupAttributesById(backendgroupId string) (map[string]string, error) {
   494  	client, err := self.GetElbV2Client()
   495  	if err != nil {
   496  		return nil, errors.Wrap(err, "GetElbV2Client")
   497  	}
   498  
   499  	params := &elbv2.DescribeTargetGroupAttributesInput{}
   500  	params.SetTargetGroupArn(backendgroupId)
   501  
   502  	output, err := client.DescribeTargetGroupAttributes(params)
   503  	if err != nil {
   504  		return nil, errors.Wrap(err, "DescribeTargetGroupAttributes")
   505  	}
   506  
   507  	attrs := []map[string]string{}
   508  	err = unmarshalAwsOutput(output, "Attributes", &attrs)
   509  	if err != nil {
   510  		return nil, errors.Wrap(err, "unmarshalAwsOutput.Attributes")
   511  	}
   512  
   513  	ret := map[string]string{}
   514  	for i := range attrs {
   515  		for k, v := range attrs[i] {
   516  			ret[k] = v
   517  		}
   518  	}
   519  
   520  	return ret, nil
   521  }