yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/loadbalancer_backendgroup.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 hcso
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"strings"
    21  	"time"
    22  
    23  	"yunion.io/x/jsonutils"
    24  	"yunion.io/x/pkg/errors"
    25  
    26  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    27  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    28  	"yunion.io/x/cloudmux/pkg/multicloud"
    29  	"yunion.io/x/cloudmux/pkg/multicloud/huawei"
    30  )
    31  
    32  type SElbBackendGroup struct {
    33  	multicloud.SResourceBase
    34  	huawei.HuaweiTags
    35  	lb     *SLoadbalancer
    36  	region *SRegion
    37  
    38  	LBAlgorithm        string        `json:"lb_algorithm"`
    39  	Protocol           string        `json:"protocol"`
    40  	Description        string        `json:"description"`
    41  	AdminStateUp       bool          `json:"admin_state_up"`
    42  	Loadbalancers      []Listener    `json:"loadbalancers"`
    43  	TenantID           string        `json:"tenant_id"`
    44  	ProjectID          string        `json:"project_id"`
    45  	Listeners          []Listener    `json:"listeners"`
    46  	ID                 string        `json:"id"`
    47  	Name               string        `json:"name"`
    48  	HealthMonitorID    string        `json:"healthmonitor_id"`
    49  	SessionPersistence StickySession `json:"session_persistence"`
    50  }
    51  
    52  func (self *SElbBackendGroup) GetLoadbalancerId() string {
    53  	return self.lb.GetId()
    54  }
    55  
    56  func (self *SElbBackendGroup) GetILoadbalancer() cloudprovider.ICloudLoadbalancer {
    57  	return self.lb
    58  }
    59  
    60  type StickySession struct {
    61  	Type               string `json:"type"`
    62  	CookieName         string `json:"cookie_name"`
    63  	PersistenceTimeout int    `json:"persistence_timeout"`
    64  }
    65  
    66  func (self *SElbBackendGroup) GetProtocolType() string {
    67  	switch self.Protocol {
    68  	case "TCP":
    69  		return api.LB_LISTENER_TYPE_TCP
    70  	case "UDP":
    71  		return api.LB_LISTENER_TYPE_UDP
    72  	case "HTTP":
    73  		return api.LB_LISTENER_TYPE_HTTP
    74  	default:
    75  		return ""
    76  	}
    77  }
    78  
    79  func (self *SElbBackendGroup) GetScheduler() string {
    80  	switch self.LBAlgorithm {
    81  	case "ROUND_ROBIN":
    82  		return api.LB_SCHEDULER_WRR
    83  	case "LEAST_CONNECTIONS":
    84  		return api.LB_SCHEDULER_WLC
    85  	case "SOURCE_IP":
    86  		return api.LB_SCHEDULER_SCH
    87  	default:
    88  		return ""
    89  	}
    90  }
    91  
    92  func ToHuaweiHealthCheckHttpCode(c string) string {
    93  	c = strings.TrimSpace(c)
    94  	segs := strings.Split(c, ",")
    95  	ret := []string{}
    96  	for _, seg := range segs {
    97  		seg = strings.TrimLeft(seg, "http_")
    98  		seg = strings.TrimSpace(seg)
    99  		seg = strings.Replace(seg, "xx", "00", -1)
   100  		ret = append(ret, seg)
   101  	}
   102  
   103  	return strings.Join(ret, ",")
   104  }
   105  
   106  func ToOnecloudHealthCheckHttpCode(c string) string {
   107  	c = strings.TrimSpace(c)
   108  	segs := strings.Split(c, ",")
   109  	ret := []string{}
   110  	for _, seg := range segs {
   111  		seg = strings.TrimSpace(seg)
   112  		seg = strings.Replace(seg, "00", "xx", -1)
   113  		seg = "http_" + seg
   114  		ret = append(ret, seg)
   115  	}
   116  
   117  	return strings.Join(ret, ",")
   118  }
   119  
   120  func (self *SElbBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) {
   121  	if len(self.HealthMonitorID) == 0 {
   122  		return nil, nil
   123  	}
   124  
   125  	health, err := self.region.GetLoadBalancerHealthCheck(self.HealthMonitorID)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	var healthCheckType string
   131  	switch health.Type {
   132  	case "TCP":
   133  		healthCheckType = api.LB_HEALTH_CHECK_TCP
   134  	case "UDP_CONNECT":
   135  		healthCheckType = api.LB_HEALTH_CHECK_UDP
   136  	case "HTTP":
   137  		healthCheckType = api.LB_HEALTH_CHECK_HTTP
   138  	default:
   139  		healthCheckType = ""
   140  	}
   141  
   142  	ret := cloudprovider.SLoadbalancerHealthCheck{
   143  		HealthCheckType:     healthCheckType,
   144  		HealthCheckTimeout:  health.Timeout,
   145  		HealthCheckDomain:   health.DomainName,
   146  		HealthCheckURI:      health.URLPath,
   147  		HealthCheckInterval: health.Delay,
   148  		HealthCheckRise:     health.MaxRetries,
   149  		HealthCheckHttpCode: ToOnecloudHealthCheckHttpCode(health.ExpectedCodes),
   150  	}
   151  
   152  	return &ret, nil
   153  }
   154  
   155  func (self *SElbBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) {
   156  	if len(self.SessionPersistence.Type) == 0 {
   157  		return nil, nil
   158  	}
   159  
   160  	var stickySessionType string
   161  	switch self.SessionPersistence.Type {
   162  	case "SOURCE_IP":
   163  		stickySessionType = api.LB_STICKY_SESSION_TYPE_INSERT
   164  	case "HTTP_COOKIE":
   165  		stickySessionType = api.LB_STICKY_SESSION_TYPE_INSERT
   166  	case "APP_COOKIE":
   167  		stickySessionType = api.LB_STICKY_SESSION_TYPE_SERVER
   168  	}
   169  
   170  	ret := cloudprovider.SLoadbalancerStickySession{
   171  		StickySession:              api.LB_BOOL_ON,
   172  		StickySessionCookie:        self.SessionPersistence.CookieName,
   173  		StickySessionType:          stickySessionType,
   174  		StickySessionCookieTimeout: self.SessionPersistence.PersistenceTimeout * 60,
   175  	}
   176  
   177  	return &ret, nil
   178  }
   179  
   180  func (self *SElbBackendGroup) GetId() string {
   181  	return self.ID
   182  }
   183  
   184  func (self *SElbBackendGroup) GetName() string {
   185  	return self.Name
   186  }
   187  
   188  func (self *SElbBackendGroup) GetGlobalId() string {
   189  	return self.GetId()
   190  }
   191  
   192  func (self *SElbBackendGroup) GetStatus() string {
   193  	return api.LB_STATUS_ENABLED
   194  }
   195  
   196  func (self *SElbBackendGroup) Refresh() error {
   197  	ret, err := self.lb.region.GetLoadBalancerBackendGroupId(self.GetId())
   198  	if err != nil {
   199  		return err
   200  	}
   201  	ret.lb = self.lb
   202  
   203  	err = jsonutils.Update(self, ret)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func (self *SElbBackendGroup) IsEmulated() bool {
   212  	return false
   213  }
   214  
   215  func (self *SElbBackendGroup) GetProjectId() string {
   216  	return self.ProjectID
   217  }
   218  
   219  func (self *SElbBackendGroup) IsDefault() bool {
   220  	return false
   221  }
   222  
   223  func (self *SElbBackendGroup) GetType() string {
   224  	return api.LB_BACKENDGROUP_TYPE_NORMAL
   225  }
   226  
   227  func (self *SElbBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) {
   228  	ret, err := self.region.GetLoadBalancerBackends(self.GetId())
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	iret := []cloudprovider.ICloudLoadbalancerBackend{}
   234  	for i := range ret {
   235  		backend := ret[i]
   236  		backend.lb = self.lb
   237  		backend.backendGroup = self
   238  
   239  		iret = append(iret, &backend)
   240  	}
   241  
   242  	return iret, nil
   243  }
   244  
   245  func (self *SElbBackendGroup) GetILoadbalancerBackendById(serverId string) (cloudprovider.ICloudLoadbalancerBackend, error) {
   246  	m := self.lb.region.ecsClient.ElbBackend
   247  	err := m.SetBackendGroupId(self.GetId())
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	backend := SElbBackend{}
   253  	err = DoGet(m.Get, serverId, nil, &backend)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	backend.lb = self.lb
   259  	backend.backendGroup = self
   260  	return &backend, nil
   261  }
   262  
   263  func (self *SElbBackendGroup) AddBackendServer(serverId string, weight int, port int) (cloudprovider.ICloudLoadbalancerBackend, error) {
   264  	instance, err := self.lb.region.GetInstanceByID(serverId)
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	nics, err := instance.GetINics()
   270  	if err != nil {
   271  		return nil, err
   272  	} else if len(nics) == 0 {
   273  		return nil, fmt.Errorf("AddBackendServer %s no network interface found", serverId)
   274  	}
   275  
   276  	subnets, err := self.lb.region.getSubnetIdsByInstanceId(instance.GetId())
   277  	if err != nil {
   278  		return nil, err
   279  	} else if len(subnets) == 0 {
   280  		return nil, fmt.Errorf("AddBackendServer %s no subnet found", serverId)
   281  	}
   282  
   283  	net, err := self.lb.region.getNetwork(subnets[0])
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  
   288  	backend, err := self.region.AddLoadBalancerBackend(self.GetId(), net.NeutronSubnetID, nics[0].GetIP(), port, weight)
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	backend.lb = self.lb
   294  	backend.backendGroup = self
   295  	return &backend, nil
   296  }
   297  
   298  func (self *SElbBackendGroup) RemoveBackendServer(backendId string, weight int, port int) error {
   299  	ibackend, err := self.GetILoadbalancerBackendById(backendId)
   300  	if err != nil {
   301  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   302  			return nil
   303  		}
   304  
   305  		return errors.Wrap(err, "ElbBackendGroup.GetILoadbalancerBackendById")
   306  	}
   307  
   308  	err = self.region.RemoveLoadBalancerBackend(self.GetId(), backendId)
   309  	if err != nil {
   310  		return errors.Wrap(err, "ElbBackendGroup.RemoveBackendServer")
   311  	}
   312  
   313  	return cloudprovider.WaitDeleted(ibackend, 2*time.Second, 30*time.Second)
   314  }
   315  
   316  func (self *SElbBackendGroup) Delete(ctx context.Context) error {
   317  	if len(self.HealthMonitorID) > 0 {
   318  		err := self.region.DeleteLoadbalancerHealthCheck(self.HealthMonitorID)
   319  		if err != nil {
   320  			return errors.Wrap(err, "ElbBackendGroup.Delete.DeleteLoadbalancerHealthCheck")
   321  		}
   322  	}
   323  
   324  	// 删除后端服务器组的同时,删除掉无效的后端服务器数据
   325  	{
   326  		backends, err := self.region.getLoadBalancerAdminStateDownBackends(self.GetId())
   327  		if err != nil {
   328  			return errors.Wrap(err, "SElbBackendGroup.Delete.getLoadBalancerAdminStateDownBackends")
   329  		}
   330  
   331  		for i := range backends {
   332  			backend := backends[i]
   333  			err := self.RemoveBackendServer(backend.GetId(), backend.GetPort(), backend.GetWeight())
   334  			if err != nil {
   335  				return errors.Wrap(err, "SElbBackendGroup.Delete.RemoveBackendServer")
   336  			}
   337  		}
   338  	}
   339  
   340  	err := self.region.DeleteLoadBalancerBackendGroup(self.GetId())
   341  	if err != nil {
   342  		return errors.Wrap(err, "ElbBackendGroup.Delete.DeleteLoadBalancerBackendGroup")
   343  	}
   344  
   345  	return cloudprovider.WaitDeleted(self, 2*time.Second, 30*time.Second)
   346  }
   347  
   348  func (self *SElbBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error {
   349  	if group == nil {
   350  		return nil
   351  	}
   352  
   353  	_, err := self.region.UpdateLoadBalancerBackendGroup(self.GetId(), group)
   354  	return err
   355  }
   356  
   357  func (self *SRegion) GetLoadBalancerBackendGroupId(backendGroupId string) (SElbBackendGroup, error) {
   358  	ret := SElbBackendGroup{}
   359  	err := DoGet(self.ecsClient.ElbBackendGroup.Get, backendGroupId, nil, &ret)
   360  	if err != nil {
   361  		return ret, err
   362  	}
   363  
   364  	ret.region = self
   365  	return ret, nil
   366  }
   367  
   368  // https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561550.html
   369  func (self *SRegion) UpdateLoadBalancerBackendGroup(backendGroupID string, group *cloudprovider.SLoadbalancerBackendGroup) (SElbBackendGroup, error) {
   370  	params := jsonutils.NewDict()
   371  	poolObj := jsonutils.NewDict()
   372  	poolObj.Set("name", jsonutils.NewString(group.Name))
   373  	var scheduler string
   374  	if s, ok := LB_ALGORITHM_MAP[group.Scheduler]; !ok {
   375  		return SElbBackendGroup{}, fmt.Errorf("UpdateLoadBalancerBackendGroup unsupported scheduler %s", group.Scheduler)
   376  	} else {
   377  		scheduler = s
   378  	}
   379  	poolObj.Set("lb_algorithm", jsonutils.NewString(scheduler))
   380  
   381  	if group.StickySession == nil || group.StickySession.StickySession == api.LB_BOOL_OFF {
   382  		poolObj.Set("session_persistence", jsonutils.JSONNull)
   383  	} else {
   384  		s := jsonutils.NewDict()
   385  		timeout := int64(group.StickySession.StickySessionCookieTimeout / 60)
   386  		if group.ListenType == api.LB_LISTENER_TYPE_UDP || group.ListenType == api.LB_LISTENER_TYPE_TCP {
   387  			s.Set("type", jsonutils.NewString("SOURCE_IP"))
   388  			if timeout > 0 {
   389  				s.Set("persistence_timeout", jsonutils.NewInt(timeout))
   390  			}
   391  		} else {
   392  			s.Set("type", jsonutils.NewString(LB_STICKY_SESSION_MAP[group.StickySession.StickySessionType]))
   393  			if len(group.StickySession.StickySessionCookie) > 0 {
   394  				s.Set("cookie_name", jsonutils.NewString(group.StickySession.StickySessionCookie))
   395  			} else {
   396  				if timeout > 0 {
   397  					s.Set("persistence_timeout", jsonutils.NewInt(timeout))
   398  				}
   399  			}
   400  		}
   401  
   402  		poolObj.Set("session_persistence", s)
   403  	}
   404  	params.Set("pool", poolObj)
   405  
   406  	ret := SElbBackendGroup{}
   407  	err := DoUpdate(self.ecsClient.ElbBackendGroup.Update, backendGroupID, params, &ret)
   408  	if err != nil {
   409  		return ret, errors.Wrap(err, "ElbBackendGroup.Update")
   410  	}
   411  
   412  	if group.HealthCheck == nil && len(ret.HealthMonitorID) > 0 {
   413  		err := self.DeleteLoadbalancerHealthCheck(ret.HealthMonitorID)
   414  		if err != nil {
   415  			return ret, errors.Wrap(err, "DeleteLoadbalancerHealthCheck")
   416  		}
   417  	}
   418  
   419  	if group.HealthCheck != nil {
   420  		if len(ret.HealthMonitorID) == 0 {
   421  			_, err := self.CreateLoadBalancerHealthCheck(ret.GetId(), group.HealthCheck)
   422  			if err != nil {
   423  				return ret, errors.Wrap(err, "CreateLoadBalancerHealthCheck")
   424  			}
   425  		} else {
   426  			_, err := self.UpdateLoadBalancerHealthCheck(ret.HealthMonitorID, group.HealthCheck)
   427  			if err != nil {
   428  				return ret, errors.Wrap(err, "UpdateLoadBalancerHealthCheck")
   429  			}
   430  		}
   431  	}
   432  
   433  	ret.region = self
   434  	return ret, nil
   435  }
   436  
   437  // https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561551.html
   438  func (self *SRegion) DeleteLoadBalancerBackendGroup(backendGroupID string) error {
   439  	return DoDelete(self.ecsClient.ElbBackendGroup.Delete, backendGroupID, nil, nil)
   440  }
   441  
   442  // https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561556.html
   443  func (self *SRegion) AddLoadBalancerBackend(backendGroupId, subnetId, ipaddr string, port, weight int) (SElbBackend, error) {
   444  	backend := SElbBackend{}
   445  	params := jsonutils.NewDict()
   446  	memberObj := jsonutils.NewDict()
   447  	memberObj.Set("address", jsonutils.NewString(ipaddr))
   448  	memberObj.Set("protocol_port", jsonutils.NewInt(int64(port)))
   449  	memberObj.Set("subnet_id", jsonutils.NewString(subnetId))
   450  	memberObj.Set("weight", jsonutils.NewInt(int64(weight)))
   451  	params.Set("member", memberObj)
   452  
   453  	m := self.ecsClient.ElbBackend
   454  	err := m.SetBackendGroupId(backendGroupId)
   455  	if err != nil {
   456  		return backend, err
   457  	}
   458  
   459  	err = DoCreate(m.Create, params, &backend)
   460  	if err != nil {
   461  		return backend, err
   462  	}
   463  
   464  	return backend, nil
   465  }
   466  
   467  func (self *SRegion) RemoveLoadBalancerBackend(lbbgId string, backendId string) error {
   468  	m := self.ecsClient.ElbBackend
   469  	err := m.SetBackendGroupId(lbbgId)
   470  	if err != nil {
   471  		return err
   472  	}
   473  
   474  	return DoDelete(m.Delete, backendId, nil, nil)
   475  }
   476  
   477  func (self *SRegion) getLoadBalancerBackends(backendGroupId string) ([]SElbBackend, error) {
   478  	m := self.ecsClient.ElbBackend
   479  	err := m.SetBackendGroupId(backendGroupId)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  
   484  	ret := []SElbBackend{}
   485  	err = doListAll(m.List, nil, &ret)
   486  	if err != nil {
   487  		return nil, err
   488  	}
   489  
   490  	for i := range ret {
   491  		backend := ret[i]
   492  		backend.region = self
   493  	}
   494  
   495  	return ret, nil
   496  }
   497  
   498  func (self *SRegion) GetLoadBalancerBackends(backendGroupId string) ([]SElbBackend, error) {
   499  	ret, err := self.getLoadBalancerBackends(backendGroupId)
   500  	if err != nil {
   501  		return nil, errors.Wrap(err, "SRegion.GetLoadBalancerBackends.getLoadBalancerBackends")
   502  	}
   503  
   504  	// 过滤掉服务器已经被删除的backend。原因是运管平台查询不到已删除的服务器记录,导致同步出错。产生肮数据。
   505  	filtedRet := []SElbBackend{}
   506  	for i := range ret {
   507  		if ret[i].AdminStateUp {
   508  			backend := ret[i]
   509  			filtedRet = append(filtedRet, backend)
   510  		}
   511  	}
   512  
   513  	return filtedRet, nil
   514  }
   515  
   516  func (self *SRegion) getLoadBalancerAdminStateDownBackends(backendGroupId string) ([]SElbBackend, error) {
   517  	ret, err := self.getLoadBalancerBackends(backendGroupId)
   518  	if err != nil {
   519  		return nil, errors.Wrap(err, "SRegion.getLoadBalancerAdminStateDownBackends.getLoadBalancerBackends")
   520  	}
   521  
   522  	filtedRet := []SElbBackend{}
   523  	for i := range ret {
   524  		if !ret[i].AdminStateUp {
   525  			backend := ret[i]
   526  			filtedRet = append(filtedRet, backend)
   527  		}
   528  	}
   529  
   530  	return filtedRet, nil
   531  }
   532  
   533  func (self *SRegion) GetLoadBalancerHealthCheck(healthCheckId string) (SElbHealthCheck, error) {
   534  	ret := SElbHealthCheck{}
   535  	err := DoGet(self.ecsClient.ElbHealthCheck.Get, healthCheckId, nil, &ret)
   536  	if err != nil {
   537  		return ret, err
   538  	}
   539  
   540  	ret.region = self
   541  	return ret, nil
   542  }