yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/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 huawei
    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  )
    31  
    32  type SElbBackendGroup struct {
    33  	multicloud.SResourceBase
    34  	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  	backend, err := self.region.GetElbBackend(self.GetId(), serverId)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	backend.lb = self.lb
   251  	backend.backendGroup = self
   252  	return backend, nil
   253  }
   254  
   255  func (self *SElbBackendGroup) AddBackendServer(serverId string, weight int, port int) (cloudprovider.ICloudLoadbalancerBackend, error) {
   256  	instance, err := self.lb.region.GetInstanceByID(serverId)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	nics, err := instance.GetINics()
   262  	if err != nil {
   263  		return nil, err
   264  	} else if len(nics) == 0 {
   265  		return nil, fmt.Errorf("AddBackendServer %s no network interface found", serverId)
   266  	}
   267  
   268  	subnets, err := self.lb.region.getSubnetIdsByInstanceId(instance.GetId())
   269  	if err != nil {
   270  		return nil, err
   271  	} else if len(subnets) == 0 {
   272  		return nil, fmt.Errorf("AddBackendServer %s no subnet found", serverId)
   273  	}
   274  
   275  	net, err := self.lb.region.getNetwork(subnets[0])
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	backend, err := self.region.AddLoadBalancerBackend(self.GetId(), net.NeutronSubnetID, nics[0].GetIP(), port, weight)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	backend.lb = self.lb
   286  	backend.backendGroup = self
   287  	return backend, nil
   288  }
   289  
   290  func (self *SElbBackendGroup) RemoveBackendServer(backendId string, weight int, port int) error {
   291  	ibackend, err := self.GetILoadbalancerBackendById(backendId)
   292  	if err != nil {
   293  		if errors.Cause(err) == cloudprovider.ErrNotFound {
   294  			return nil
   295  		}
   296  
   297  		return errors.Wrap(err, "ElbBackendGroup.GetILoadbalancerBackendById")
   298  	}
   299  
   300  	err = self.region.RemoveLoadBalancerBackend(self.GetId(), backendId)
   301  	if err != nil {
   302  		return errors.Wrap(err, "ElbBackendGroup.RemoveBackendServer")
   303  	}
   304  
   305  	return cloudprovider.WaitDeleted(ibackend, 2*time.Second, 30*time.Second)
   306  }
   307  
   308  func (self *SElbBackendGroup) Delete(ctx context.Context) error {
   309  	if len(self.HealthMonitorID) > 0 {
   310  		err := self.region.DeleteLoadbalancerHealthCheck(self.HealthMonitorID)
   311  		if err != nil {
   312  			return errors.Wrap(err, "ElbBackendGroup.Delete.DeleteLoadbalancerHealthCheck")
   313  		}
   314  	}
   315  
   316  	// 删除后端服务器组的同时,删除掉无效的后端服务器数据
   317  	{
   318  		backends, err := self.region.getLoadBalancerAdminStateDownBackends(self.GetId())
   319  		if err != nil {
   320  			return errors.Wrap(err, "SElbBackendGroup.Delete.getLoadBalancerAdminStateDownBackends")
   321  		}
   322  
   323  		for i := range backends {
   324  			backend := backends[i]
   325  			err := self.RemoveBackendServer(backend.GetId(), backend.GetPort(), backend.GetWeight())
   326  			if err != nil {
   327  				return errors.Wrap(err, "SElbBackendGroup.Delete.RemoveBackendServer")
   328  			}
   329  		}
   330  	}
   331  
   332  	err := self.region.DeleteLoadBalancerBackendGroup(self.GetId())
   333  	if err != nil {
   334  		return errors.Wrap(err, "ElbBackendGroup.Delete.DeleteLoadBalancerBackendGroup")
   335  	}
   336  
   337  	return cloudprovider.WaitDeleted(self, 2*time.Second, 30*time.Second)
   338  }
   339  
   340  func (self *SElbBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error {
   341  	if group == nil {
   342  		return nil
   343  	}
   344  
   345  	_, err := self.region.UpdateLoadBalancerBackendGroup(self.GetId(), group)
   346  	return err
   347  }
   348  
   349  func (self *SRegion) GetLoadBalancerBackendGroupId(backendGroupId string) (*SElbBackendGroup, error) {
   350  	ret := &SElbBackendGroup{region: self}
   351  	res := fmt.Sprintf("elb/pools/" + backendGroupId)
   352  	resp, err := self.lbGet(res)
   353  	if err != nil {
   354  		return nil, err
   355  	}
   356  	return ret, resp.Unmarshal(ret, "pool")
   357  }
   358  
   359  // https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561550.html
   360  func (self *SRegion) UpdateLoadBalancerBackendGroup(backendGroupID string, group *cloudprovider.SLoadbalancerBackendGroup) (*SElbBackendGroup, error) {
   361  	params := map[string]interface{}{
   362  		"name": group.Name,
   363  	}
   364  	var scheduler string
   365  	if s, ok := LB_ALGORITHM_MAP[group.Scheduler]; !ok {
   366  		return nil, fmt.Errorf("UpdateLoadBalancerBackendGroup unsupported scheduler %s", group.Scheduler)
   367  	} else {
   368  		scheduler = s
   369  	}
   370  	params["lb_algorithm"] = scheduler
   371  
   372  	if group.StickySession == nil || group.StickySession.StickySession == api.LB_BOOL_OFF {
   373  		params["session_persistence"] = jsonutils.JSONNull
   374  	} else {
   375  		s := map[string]interface{}{}
   376  		timeout := int64(group.StickySession.StickySessionCookieTimeout / 60)
   377  		if group.ListenType == api.LB_LISTENER_TYPE_UDP || group.ListenType == api.LB_LISTENER_TYPE_TCP {
   378  			s["type"] = "SOURCE_IP"
   379  			if timeout > 0 {
   380  				s["persistence_timeout"] = timeout
   381  			}
   382  		} else {
   383  			s["type"] = LB_STICKY_SESSION_MAP[group.StickySession.StickySessionType]
   384  			if len(group.StickySession.StickySessionCookie) > 0 {
   385  				s["cookie_name"] = group.StickySession.StickySessionCookie
   386  			} else {
   387  				if timeout > 0 {
   388  					s["persistence_timeout"] = timeout
   389  				}
   390  			}
   391  		}
   392  		params["session_persistence"] = s
   393  	}
   394  	resp, err := self.lbUpdate("elb/pools/"+backendGroupID, map[string]interface{}{"pool": params})
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	ret := &SElbBackendGroup{}
   400  	err = resp.Unmarshal(ret, "pool")
   401  	if err != nil {
   402  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   403  	}
   404  
   405  	if group.HealthCheck == nil && len(ret.HealthMonitorID) > 0 {
   406  		err := self.DeleteLoadbalancerHealthCheck(ret.HealthMonitorID)
   407  		if err != nil {
   408  			return ret, errors.Wrap(err, "DeleteLoadbalancerHealthCheck")
   409  		}
   410  	}
   411  
   412  	if group.HealthCheck != nil {
   413  		if len(ret.HealthMonitorID) == 0 {
   414  			_, err := self.CreateLoadBalancerHealthCheck(ret.GetId(), group.HealthCheck)
   415  			if err != nil {
   416  				return ret, errors.Wrap(err, "CreateLoadBalancerHealthCheck")
   417  			}
   418  		} else {
   419  			_, err := self.UpdateLoadBalancerHealthCheck(ret.HealthMonitorID, group.HealthCheck)
   420  			if err != nil {
   421  				return ret, errors.Wrap(err, "UpdateLoadBalancerHealthCheck")
   422  			}
   423  		}
   424  	}
   425  
   426  	ret.region = self
   427  	return ret, nil
   428  }
   429  
   430  // https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561551.html
   431  func (self *SRegion) DeleteLoadBalancerBackendGroup(id string) error {
   432  	_, err := self.lbDelete("elb/pools/" + id)
   433  	return err
   434  }
   435  
   436  // https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561556.html
   437  func (self *SRegion) AddLoadBalancerBackend(backendGroupId, subnetId, ipaddr string, port, weight int) (*SElbBackend, error) {
   438  	params := map[string]interface{}{
   439  		"address":       ipaddr,
   440  		"protocol_port": port,
   441  		"subnet_id":     subnetId,
   442  		"weight":        weight,
   443  	}
   444  	ret := &SElbBackend{}
   445  	resp, err := self.lbCreate(fmt.Sprintf("elb/pools/%s/members", backendGroupId), map[string]interface{}{"member": params})
   446  	if err != nil {
   447  		return nil, err
   448  	}
   449  	return ret, resp.Unmarshal(ret, "member")
   450  }
   451  
   452  func (self *SRegion) RemoveLoadBalancerBackend(lbbgId string, backendId string) error {
   453  	_, err := self.lbDelete(fmt.Sprintf("elb/pools/%s/members/%s", lbbgId, backendId))
   454  	return err
   455  }
   456  
   457  func (self *SRegion) getLoadBalancerBackends(backendGroupId string) ([]SElbBackend, error) {
   458  	res := fmt.Sprintf("elb/pools/%s/members", backendGroupId)
   459  	resp, err := self.lbList(res, url.Values{})
   460  	if err != nil {
   461  		return nil, err
   462  	}
   463  	ret := []SElbBackend{}
   464  	return ret, resp.Unmarshal(&ret, "members")
   465  }
   466  
   467  func (self *SRegion) GetLoadBalancerBackends(backendGroupId string) ([]SElbBackend, error) {
   468  	ret, err := self.getLoadBalancerBackends(backendGroupId)
   469  	if err != nil {
   470  		return nil, errors.Wrap(err, "SRegion.GetLoadBalancerBackends.getLoadBalancerBackends")
   471  	}
   472  
   473  	// 过滤掉服务器已经被删除的backend。原因是运管平台查询不到已删除的服务器记录,导致同步出错。产生肮数据。
   474  	filtedRet := []SElbBackend{}
   475  	for i := range ret {
   476  		if ret[i].AdminStateUp {
   477  			backend := ret[i]
   478  			filtedRet = append(filtedRet, backend)
   479  		}
   480  	}
   481  
   482  	return filtedRet, nil
   483  }
   484  
   485  func (self *SRegion) getLoadBalancerAdminStateDownBackends(backendGroupId string) ([]SElbBackend, error) {
   486  	ret, err := self.getLoadBalancerBackends(backendGroupId)
   487  	if err != nil {
   488  		return nil, errors.Wrap(err, "SRegion.getLoadBalancerAdminStateDownBackends.getLoadBalancerBackends")
   489  	}
   490  
   491  	filtedRet := []SElbBackend{}
   492  	for i := range ret {
   493  		if !ret[i].AdminStateUp {
   494  			backend := ret[i]
   495  			filtedRet = append(filtedRet, backend)
   496  		}
   497  	}
   498  
   499  	return filtedRet, nil
   500  }
   501  
   502  func (self *SRegion) GetLoadBalancerHealthCheck(healthCheckId string) (*SElbHealthCheck, error) {
   503  	resp, err := self.lbGet("elb/healthmonitors/" + healthCheckId)
   504  	if err != nil {
   505  		return nil, err
   506  	}
   507  	ret := &SElbHealthCheck{region: self}
   508  	return ret, resp.Unmarshal(ret, "healthmonitor")
   509  }