yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/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 qcloud 16 17 import ( 18 "context" 19 "fmt" 20 "strconv" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 "yunion.io/x/pkg/utils" 28 29 api "yunion.io/x/cloudmux/pkg/apis/compute" 30 "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 ) 34 35 const ( 36 LB_ADDR_TYPE_INTERNAL = "INTERNAL" 37 LB_ADDR_TYPE_OPEN = "OPEN" 38 ) 39 40 type LB_TYPE int64 41 42 const ( 43 LB_TYPE_CLASSIC = LB_TYPE(0) 44 LB_TYPE_APPLICATION = LB_TYPE(1) 45 ) 46 47 /* 48 todo: 49 1.统一LB 证书fingerprint算法.另外缺少一个回写指纹算法到数据库的方法。 50 2.需要同步腾讯云LB 所在的project 51 */ 52 53 // https://cloud.tencent.com/document/api/214/30694#LoadBalancer 54 type SLoadbalancer struct { 55 multicloud.SLoadbalancerBase 56 QcloudTags 57 region *SRegion 58 59 Status int64 `json:"Status"` // 0:创建中,1:正常运行 60 Domain string `json:"Domain"` 61 VpcID string `json:"VpcId"` 62 Log string `json:"Log"` 63 ProjectID int64 `json:"ProjectId"` 64 Snat bool `json:"Snat"` 65 LoadBalancerID string `json:"LoadBalancerId"` 66 LoadBalancerVips []string `json:"LoadBalancerVips"` 67 LoadBalancerType string `json:"LoadBalancerType"` // 负载均衡实例的网络类型: OPEN:公网属性, INTERNAL:内网属性。 68 LoadBalancerName string `json:"LoadBalancerName"` 69 Forward LB_TYPE `json:"Forward"` // 应用型负载均衡标识,1:应用型负载均衡,0:传统型的负载均衡。 70 StatusTime time.Time `json:"StatusTime"` 71 OpenBGP int64 `json:"OpenBgp"` // 高防 LB 的标识,1:高防负载均衡 0:非高防负载均衡。 72 CreateTime time.Time `json:"CreateTime"` 73 Isolation int64 `json:"Isolation"` // 0:表示未被隔离,1:表示被隔离。 74 SubnetId string `json:"SubnetId"` 75 BackupZoneSet []ZoneSet `json:"BackupZoneSet"` 76 MasterZone ZoneSet `json:"MasterZone"` 77 NetworkAttributes struct { 78 InternetChargeType string 79 InternetMaxBandwidthOut int 80 } 81 } 82 83 type ZoneSet struct { 84 Zone string `json:"Zone"` 85 ZoneID int64 `json:"ZoneId"` 86 ZoneName string `json:"ZoneName"` 87 } 88 89 func (self *SLoadbalancer) GetLoadbalancerSpec() string { 90 return "" 91 } 92 93 func (self *SLoadbalancer) GetChargeType() string { 94 if len(self.NetworkAttributes.InternetChargeType) > 0 && self.NetworkAttributes.InternetChargeType != "TRAFFIC_POSTPAID_BY_HOUR" { 95 return api.LB_CHARGE_TYPE_BY_BANDWIDTH 96 } 97 return api.LB_CHARGE_TYPE_BY_TRAFFIC 98 } 99 100 func (self *SLoadbalancer) GetEgressMbps() int { 101 return self.NetworkAttributes.InternetMaxBandwidthOut 102 } 103 104 // https://cloud.tencent.com/document/product/214/30689 105 func (self *SLoadbalancer) Delete(ctx context.Context) error { 106 lockman.LockRawObject(ctx, "qcloud.SLoadbalancer.Delete", self.region.client.ownerId) 107 defer lockman.ReleaseRawObject(ctx, "qcloud.SLoadbalancer.Delete", self.region.client.ownerId) 108 109 if self.Forward == LB_TYPE_APPLICATION { 110 _, err := self.region.DeleteLoadbalancer(self.GetId()) 111 if err != nil { 112 return err 113 } 114 } else { 115 _, err := self.region.DeleteClassicLoadbalancer(self.GetId()) 116 if err != nil { 117 return err 118 } 119 } 120 121 return cloudprovider.WaitDeleted(self, 5*time.Second, 60*time.Second) 122 } 123 124 // 腾讯云loadbalance不支持启用/禁用 125 func (self *SLoadbalancer) Start() error { 126 return nil 127 } 128 129 func (self *SLoadbalancer) Stop() error { 130 return cloudprovider.ErrNotSupported 131 } 132 133 // 腾讯云无后端服务器组 134 // todo: 是否返回一个fake的后端服务器组 135 func (self *SLoadbalancer) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { 136 return nil, cloudprovider.ErrNotSupported 137 } 138 139 func (self *SLoadbalancer) GetILoadBalancerBackendGroupById(groupId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { 140 groups, err := self.GetILoadBalancerBackendGroups() 141 if err != nil { 142 return nil, err 143 } 144 145 for _, group := range groups { 146 if group.GetId() == groupId { 147 return group, nil 148 } 149 } 150 151 return nil, cloudprovider.ErrNotFound 152 } 153 154 func onecloudHealthCodeToQcloud(codes string) int { 155 qcode := 0 156 for i, code := range HTTP_CODES { 157 if strings.Contains(codes, code) { 158 // 按位或然后再赋值qcode 159 qcode |= 1 << uint(i) 160 } 161 } 162 163 return qcode 164 } 165 166 // https://cloud.tencent.com/document/product/214/30693 167 // todo: 1.限制比较多必须加参数校验 2.Onecloud 不支持双向证书可能存在兼容性问题 168 // 应用型负载均衡 传统型不支持设置SNI 169 func (self *SLoadbalancer) CreateILoadBalancerListener(ctx context.Context, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { 170 lockman.LockRawObject(ctx, "qcloud.SLoadbalancer.CreateILoadBalancerListener", self.region.client.ownerId) 171 defer lockman.ReleaseRawObject(ctx, "qcloud.SLoadbalancer.CreateILoadBalancerListener", self.region.client.ownerId) 172 173 sniSwitch := 0 174 hc := getHealthCheck(listener) 175 cert := getCertificate(listener) 176 177 var listenId string 178 var err error 179 if self.Forward == LB_TYPE_APPLICATION { 180 listenId, err = self.region.CreateLoadbalancerListener(self.GetId(), 181 listener.Name, 182 getProtocol(listener), 183 listener.ListenerPort, 184 getScheduler(listener), 185 &listener.StickySessionCookieTimeout, 186 &sniSwitch, 187 hc, 188 cert) 189 } else { 190 // 传统型内网属性负载均衡不支持指定scheduler 191 var scheduler *string 192 if self.LoadBalancerType == "OPEN" { 193 scheduler = getScheduler(listener) 194 } 195 196 listenId, err = self.region.CreateClassicLoadbalancerListener(self.GetId(), 197 listener.Name, 198 getClassicLBProtocol(listener), 199 listener.ListenerPort, 200 listener.BackendServerPort, 201 scheduler, 202 &listener.StickySessionCookieTimeout, 203 &sniSwitch, 204 hc, 205 cert) 206 } 207 if err != nil { 208 return nil, err 209 } 210 211 var lblis cloudprovider.ICloudLoadbalancerListener 212 err = cloudprovider.Wait(3*time.Second, 30*time.Second, func() (bool, error) { 213 lblis, err = self.GetILoadBalancerListenerById(listenId) 214 if err != nil { 215 if errors.Cause(err) != cloudprovider.ErrNotFound { 216 return false, err 217 } else { 218 return false, nil 219 } 220 } 221 222 return true, nil 223 }) 224 if err != nil { 225 return nil, errors.Wrap(err, "GetILoadBalancerListenerById.Wait") 226 } 227 228 return lblis, nil 229 } 230 231 func (self *SLoadbalancer) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { 232 listeners, err := self.GetLoadbalancerListeners("") 233 if err != nil { 234 return nil, err 235 } 236 237 for _, listener := range listeners { 238 if listener.GetId() == listenerId { 239 return &listener, nil 240 } 241 } 242 243 return nil, cloudprovider.ErrNotFound 244 } 245 246 func (self *SLoadbalancer) GetId() string { 247 return self.LoadBalancerID 248 } 249 250 func (self *SLoadbalancer) GetName() string { 251 return self.LoadBalancerName 252 } 253 254 // add region? 255 func (self *SLoadbalancer) GetGlobalId() string { 256 return self.LoadBalancerID 257 } 258 259 func (self *SLoadbalancer) GetStatus() string { 260 switch self.Status { 261 case 0: 262 return api.LB_STATUS_INIT 263 case 1: 264 return api.LB_STATUS_ENABLED 265 default: 266 return api.LB_STATUS_UNKNOWN 267 } 268 } 269 270 func (self *SLoadbalancer) Refresh() error { 271 lb, err := self.region.GetLoadbalancer(self.GetId()) 272 if err != nil { 273 return err 274 } 275 276 return jsonutils.Update(self, lb) 277 } 278 279 func (self *SLoadbalancer) IsEmulated() bool { 280 return false 281 } 282 283 func (self *SLoadbalancer) GetSysTags() map[string]string { 284 meta := map[string]string{} 285 meta["Forward"] = strconv.FormatInt(int64(self.Forward), 10) 286 meta["OpenBGP"] = strconv.FormatInt(self.OpenBGP, 10) 287 meta["Domain"] = self.Domain 288 meta["ProjectID"] = strconv.FormatInt(self.ProjectID, 10) 289 return meta 290 } 291 292 // 腾讯云当前不支持一个LB绑定多个ip,每个LB只支持绑定一个ip 293 func (self *SLoadbalancer) GetAddress() string { 294 return self.LoadBalancerVips[0] 295 } 296 297 func (self *SLoadbalancer) GetAddressType() string { 298 switch self.LoadBalancerType { 299 case LB_ADDR_TYPE_INTERNAL: 300 return api.LB_ADDR_TYPE_INTRANET 301 case LB_ADDR_TYPE_OPEN: 302 return api.LB_ADDR_TYPE_INTERNET 303 default: 304 return "" 305 } 306 } 307 308 func (self *SLoadbalancer) GetNetworkType() string { 309 if len(self.VpcID) > 0 { 310 return api.LB_NETWORK_TYPE_VPC 311 } else { 312 return api.LB_NETWORK_TYPE_CLASSIC 313 } 314 } 315 316 func (self *SLoadbalancer) GetNetworkIds() []string { 317 if len(self.SubnetId) == 0 { 318 return []string{} 319 } 320 321 return []string{self.SubnetId} 322 } 323 324 func (self *SLoadbalancer) GetVpcId() string { 325 return self.VpcID 326 } 327 328 func (self *SLoadbalancer) GetZoneId() string { 329 zoneId := "" 330 if len(self.MasterZone.Zone) > 0 { 331 zoneId = self.MasterZone.Zone 332 } else if len(self.SubnetId) > 0 { 333 net, err := self.region.GetNetwork(self.SubnetId) 334 if err != nil { 335 log.Warningf("GetNetwork %s %s", self.SubnetId, err) 336 return "" 337 } 338 339 zoneId = net.Zone 340 } 341 342 if len(zoneId) > 0 { 343 z, err := self.region.getZoneById(zoneId) 344 if err != nil { 345 log.Warningf("getZoneById %s %s", zoneId, err) 346 return "" 347 } 348 349 return z.GetGlobalId() 350 } 351 352 return "" 353 } 354 355 func (self *SLoadbalancer) GetZone1Id() string { 356 if self.BackupZoneSet == nil { 357 return "" 358 } 359 360 if len(self.BackupZoneSet) > 0 { 361 z, err := self.region.getZoneById(self.BackupZoneSet[0].Zone) 362 if err != nil { 363 log.Warningf("getZoneById %s %s", self.BackupZoneSet[0].Zone, err) 364 return "" 365 } 366 367 return z.GetGlobalId() 368 } 369 370 return "" 371 } 372 373 func (self *SLoadbalancer) GetLoadbalancerListeners(protocal string) ([]SLBListener, error) { 374 listeners, err := self.region.GetLoadbalancerListeners(self.GetId(), self.Forward, protocal) 375 if err != nil { 376 return nil, err 377 } 378 379 for i := range listeners { 380 listeners[i].lb = self 381 } 382 383 return listeners, nil 384 } 385 386 func (self *SLoadbalancer) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { 387 listeners, err := self.GetLoadbalancerListeners("") 388 if err != nil { 389 return nil, err 390 } 391 392 ilisteners := make([]cloudprovider.ICloudLoadbalancerListener, len(listeners)) 393 for i := range listeners { 394 l := listeners[i] 395 ilisteners[i] = &l 396 } 397 398 return ilisteners, nil 399 } 400 401 func (self *SLoadbalancer) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { 402 if self.Forward == LB_TYPE_CLASSIC { 403 bg := SLBBackendGroup{lb: self} 404 return []cloudprovider.ICloudLoadbalancerBackendGroup{&bg}, nil 405 } 406 407 listeners, err := self.GetLoadbalancerListeners("") 408 if err != nil { 409 return nil, err 410 } 411 412 bgs := []cloudprovider.ICloudLoadbalancerBackendGroup{} 413 for i := range listeners { 414 listener := listeners[i] 415 t := listener.GetListenerType() 416 if t == api.LB_LISTENER_TYPE_HTTP || t == api.LB_LISTENER_TYPE_HTTPS { 417 rules := listener.Rules 418 for i := range rules { 419 rule := rules[i] 420 rule.listener = &listener 421 bg := rule.GetBackendGroup() 422 bgs = append(bgs, bg) 423 } 424 } else { 425 bg := listener.GetBackendGroup() 426 bgs = append(bgs, bg) 427 } 428 } 429 430 ibgs := make([]cloudprovider.ICloudLoadbalancerBackendGroup, len(bgs)) 431 for i := range bgs { 432 ibgs[i] = bgs[i] 433 } 434 435 return ibgs, nil 436 } 437 438 func (self *SLoadbalancer) GetIEIP() (cloudprovider.ICloudEIP, error) { 439 if self.LoadBalancerType == "OPEN" && len(self.LoadBalancerVips) > 0 { 440 return &SEipAddress{ 441 region: self.region, 442 AddressId: self.LoadBalancerID, 443 AddressIp: self.LoadBalancerVips[0], 444 AddressType: EIP_STATUS_BIND, 445 InstanceId: self.LoadBalancerID, 446 CreatedTime: self.CreateTime, 447 }, nil 448 } 449 return nil, nil 450 } 451 452 func (self *SRegion) GetLoadbalancers(ids []string) ([]SLoadbalancer, error) { 453 params := map[string]string{} 454 for i, id := range ids { 455 params[fmt.Sprintf("LoadBalancerIds.%d", i)] = id 456 } 457 458 offset := 0 459 limit := 100 460 lbs := make([]SLoadbalancer, 0) 461 for { 462 params["Limit"] = strconv.Itoa(limit) 463 params["Offset"] = strconv.Itoa(offset) 464 465 resp, err := self.clbRequest("DescribeLoadBalancers", params) 466 if err != nil { 467 return nil, err 468 } 469 470 parts := make([]SLoadbalancer, 0) 471 err = resp.Unmarshal(&parts, "LoadBalancerSet") 472 if err != nil { 473 return nil, err 474 } 475 476 _total, err := resp.Float("TotalCount") 477 if err != nil { 478 return nil, err 479 } 480 481 total := int(_total) 482 if err != nil { 483 return nil, err 484 } 485 486 lbs = append(lbs, parts...) 487 offset += len(parts) 488 if offset >= total { 489 for i := range lbs { 490 lbs[i].region = self 491 } 492 493 return lbs, err 494 } 495 } 496 } 497 498 func (self *SRegion) GetLoadbalancer(id string) (*SLoadbalancer, error) { 499 if len(id) == 0 { 500 return nil, fmt.Errorf("GetILoadbalancer id should not empty") 501 } 502 503 lbs, err := self.GetLoadbalancers([]string{id}) 504 if err != nil { 505 return nil, err 506 } 507 508 switch len(lbs) { 509 case 0: 510 return nil, cloudprovider.ErrNotFound 511 case 1: 512 return &lbs[0], nil 513 default: 514 return nil, fmt.Errorf("GetILoadbalancer %s found %d", id, len(lbs)) 515 } 516 } 517 518 /* 519 返回requstid 用于异步任务查询 520 https://cloud.tencent.com/document/product/214/30689 521 */ 522 func (self *SRegion) DeleteLoadbalancer(lbid string) (string, error) { 523 if len(lbid) == 0 { 524 return "", fmt.Errorf("loadbalancer id should not be empty") 525 } 526 527 params := map[string]string{"LoadBalancerIds.0": lbid} 528 resp, err := self.clbRequest("DeleteLoadBalancer", params) 529 if err != nil { 530 return "", err 531 } 532 533 return resp.GetString("RequestId") 534 } 535 536 /* 537 返回requstid 用于异步任务查询 538 https://cloud.tencent.com/document/product/214/30689 539 */ 540 func (self *SRegion) DeleteClassicLoadbalancer(lbid string) (string, error) { 541 if len(lbid) == 0 { 542 return "", fmt.Errorf("loadbalancer id should not be empty") 543 } 544 545 params := map[string]string{"loadBalancerIds.n": lbid} 546 resp, err := self.lbRequest("DeleteLoadBalancers", params) 547 if err != nil { 548 return "", err 549 } 550 551 return resp.GetString("requestId") 552 } 553 554 /* 555 https://cloud.tencent.com/document/product/214/30693 556 SNI 特性是什么?? 557 */ 558 func (self *SRegion) CreateLoadbalancerListener(lbid, name, protocol string, port int, scheduler *string, sessionExpireTime, sniSwitch *int, healthCheck *healthCheck, cert *certificate) (string, error) { 559 if len(lbid) == 0 { 560 return "", fmt.Errorf("loadbalancer id should not be empty") 561 } 562 563 params := map[string]string{ 564 "LoadBalancerId": lbid, 565 "Ports.0": strconv.Itoa(port), 566 "Protocol": protocol, 567 } 568 569 if len(name) > 0 { 570 params["ListenerNames.0"] = name 571 } 572 573 if utils.IsInStringArray(protocol, []string{PROTOCOL_TCP, PROTOCOL_UDP, PROTOCOL_TCP_SSL}) { 574 params = healthCheckParams(LB_TYPE_APPLICATION, params, healthCheck, "HealthCheck.") 575 576 if scheduler != nil && len(*scheduler) > 0 { 577 params["Scheduler"] = *scheduler 578 } 579 580 if sessionExpireTime != nil { 581 params["SessionExpireTime"] = strconv.Itoa(*sessionExpireTime) 582 } 583 } else { 584 if protocol == PROTOCOL_HTTPS && sniSwitch != nil { 585 params["SniSwitch"] = strconv.Itoa(*sniSwitch) 586 } 587 } 588 589 params = certificateParams(LB_TYPE_APPLICATION, params, cert, "Certificate.") 590 591 resp, err := self.clbRequest("CreateListener", params) 592 if err != nil { 593 return "", err 594 } 595 596 listeners, err := resp.GetArray("ListenerIds") 597 if err != nil { 598 return "", err 599 } 600 601 if len(listeners) == 0 { 602 return "", fmt.Errorf("CreateLoadbalancerListener no listener id returned: %s", resp.String()) 603 } else if len(listeners) == 1 { 604 return listeners[0].GetString() 605 } else { 606 return "", fmt.Errorf("CreateLoadbalancerListener mutliple listener id returned: %s", resp.String()) 607 } 608 } 609 610 // https://cloud.tencent.com/document/api/214/1255 611 // 不支持sniSwitch 612 // todo: 待测试 613 func (self *SRegion) CreateClassicLoadbalancerListener(lbid, name string, protocol, port, backendServerPort int, scheduler *string, sessionExpireTime, sniSwitch *int, healthCheck *healthCheck, cert *certificate) (string, error) { 614 if len(lbid) == 0 { 615 return "", fmt.Errorf("loadbalancer id should not be empty") 616 } 617 618 // 负载均衡实例监听器协议类型 1:HTTP,2:TCP,3:UDP,4:HTTPS。 619 // todo: 待测试 。 这里没有判断是否为公网负载均衡,可能存在问题.内网传统型负载均衡监听协议只支持TCP、UDP,并且不能指定调度算法 620 params := map[string]string{ 621 "loadBalancerId": lbid, 622 "listeners.0.loadBalancerPort": strconv.Itoa(port), 623 "listeners.0.instancePort": strconv.Itoa(backendServerPort), 624 "listeners.0.protocol": strconv.Itoa(protocol), 625 } 626 627 if len(name) > 0 { 628 params["listeners.0.listenerName"] = name 629 } 630 631 if sessionExpireTime != nil { 632 params["listeners.0.sessionExpire"] = strconv.Itoa(*sessionExpireTime) 633 } 634 635 if scheduler != nil && len(*scheduler) > 0 && (protocol == 2 || protocol == 3) { 636 params["listeners.0.scheduler"] = strings.ToLower(*scheduler) 637 } 638 639 if scheduler != nil && len(*scheduler) > 0 && (protocol == 1 || protocol == 4) { 640 params["listeners.0.httpHash"] = strings.ToLower(*scheduler) 641 } 642 643 params = healthCheckParams(LB_TYPE_CLASSIC, params, healthCheck, "listeners.0.") 644 params = certificateParams(LB_TYPE_CLASSIC, params, cert, "listeners.0.") 645 646 resp, err := self.lbRequest("CreateLoadBalancerListeners", params) 647 if err != nil { 648 return "", err 649 } 650 651 listeners, err := resp.GetArray("listenerIds") 652 if err != nil { 653 return "", err 654 } 655 656 if len(listeners) == 0 { 657 return "", fmt.Errorf("CreateLoadbalancerListener no listener id returned: %s", resp.String()) 658 } else if len(listeners) == 1 { 659 return listeners[0].GetString() 660 } else { 661 return "", fmt.Errorf("CreateLoadbalancerListener mutliple listener id returned: %s", resp.String()) 662 } 663 } 664 665 // https://cloud.tencent.com/document/product/214/30683 666 // 任务的当前状态。 0:成功,1:失败,2:进行中 667 func (self *SRegion) GetLBTaskStatus(requestId string) (string, error) { 668 if len(requestId) == 0 { 669 return "", fmt.Errorf("WaitTaskSuccess requestId should not be emtpy") 670 } 671 672 params := map[string]string{"TaskId": requestId} 673 resp, err := self.clbRequest("DescribeTaskStatus", params) 674 if err != nil { 675 return "", err 676 } 677 678 status, err := resp.Get("Status") 679 if err != nil { 680 log.Debugf("WaitTaskSuccess failed %s: %s", err, resp.String()) 681 return "", err 682 } 683 684 _status, err := status.Float() 685 return fmt.Sprintf("%1.f", _status), err 686 } 687 688 func (self *SRegion) WaitLBTaskSuccess(requestId string, interval time.Duration, timeout time.Duration) error { 689 startTime := time.Now() 690 for time.Now().Sub(startTime) < timeout { 691 status, err := self.GetLBTaskStatus(requestId) 692 if err != nil { 693 return err 694 } 695 if status == "0" { 696 return nil 697 } 698 699 if status == "1" { 700 return fmt.Errorf("Task %s failed.", requestId) 701 } 702 703 time.Sleep(interval) 704 } 705 706 return cloudprovider.ErrTimeout 707 } 708 709 func (self *SLoadbalancer) GetProjectId() string { 710 return strconv.Itoa(int(self.ProjectID)) 711 } 712 713 func (self *SLoadbalancer) SetTags(tags map[string]string, replace bool) error { 714 return self.region.SetResourceTags("clb", "clb", []string{self.LoadBalancerID}, tags, replace) 715 }