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