diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 268939f07c..0bcf25d049 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -2133,7 +2133,7 @@ func getCloudNicNetwork(vnic cloudprovider.ICloudNic, host *SHost) (*SNetwork, e return nil, fmt.Errorf("Cannot find inetwork for vnics %s %s", vnic.GetMAC(), vnic.GetIP()) } else { // find network by IP - return host.getNetworkOfIPOnHost(vnic.GetIP()) + return host.getNetworkOfIPOnHost(ip) } } localNetObj, err := NetworkManager.FetchByExternalId(vnet.GetGlobalId()) diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index d73202e0fe..13f664d5ac 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -1670,15 +1670,15 @@ func (self *SHost) SyncHostVMs(ctx context.Context, userCred mcclient.TokenCrede } func (self *SHost) getNetworkOfIPOnHost(ipAddr string) (*SNetwork, error) { - net, err := NetworkManager.GetNetworkOfIP(ipAddr, "", tristate.None) - if err != nil { - return nil, err + netInterfaces := self.GetNetInterfaces() + for _, netInterface := range netInterfaces { + network, err := netInterface.GetCandidateNetworkForIp(auth.AdminCredential(), ipAddr) + if err == nil && network != nil { + return network, nil + } } - hw := self.getHostwireOfId(net.WireId) - if hw == nil { - return nil, fmt.Errorf("IP %s not reachable on this host", ipAddr) - } - return net, nil + + return nil, fmt.Errorf("IP %s not reachable on this host", ipAddr) } func (self *SHost) GetNetinterfaceWithIdAndCredential(netId string, userCred mcclient.TokenCredential, reserved bool) (*SNetInterface, *SNetwork) { diff --git a/pkg/compute/models/loadbalancer_const.go b/pkg/compute/models/loadbalancer_const.go index f6c7b597ba..6712149363 100644 --- a/pkg/compute/models/loadbalancer_const.go +++ b/pkg/compute/models/loadbalancer_const.go @@ -181,6 +181,7 @@ var LB_HEALTH_CHECK_TYPES_UDP = validators.NewChoices( ) const ( + LB_HEALTH_CHECK_HTTP_CODE_1xx = "http_1xx" LB_HEALTH_CHECK_HTTP_CODE_2xx = "http_2xx" LB_HEALTH_CHECK_HTTP_CODE_3xx = "http_3xx" LB_HEALTH_CHECK_HTTP_CODE_4xx = "http_4xx" @@ -189,6 +190,7 @@ const ( ) var LB_HEALTH_CHECK_HTTP_CODES = validators.NewChoices( + LB_HEALTH_CHECK_HTTP_CODE_1xx, LB_HEALTH_CHECK_HTTP_CODE_2xx, LB_HEALTH_CHECK_HTTP_CODE_3xx, LB_HEALTH_CHECK_HTTP_CODE_4xx, diff --git a/pkg/compute/models/loadbalancerbackendgroups.go b/pkg/compute/models/loadbalancerbackendgroups.go index a7e7ace419..9fdf4a5363 100644 --- a/pkg/compute/models/loadbalancerbackendgroups.go +++ b/pkg/compute/models/loadbalancerbackendgroups.go @@ -177,27 +177,54 @@ func (lbbg *SLoadbalancerBackendGroup) GetIRegion() (cloudprovider.ICloudRegion, return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) } +func (lbbg *SLoadbalancerBackendGroup) GetBackends() ([]SLoadbalancerBackend, error) { + backends := make([]SLoadbalancerBackend, 0) + q := LoadbalancerBackendManager.Query() + err := q.Equals("backend_group_id", lbbg.GetId()).All(&backends) + return backends, err +} + +// 返回值 TotalRef +func (lbbg *SLoadbalancerBackendGroup) RefCount() int { + men := lbbg.getRefManagers() + var count int + for _, m := range men { + count += lbbg.refCount(m) + } + + return count +} + +func (lbbg *SLoadbalancerBackendGroup) refCount(men db.IModelManager) int { + t := men.TableSpec().Instance() + pdF := t.Field("pending_deleted") + return t.Query(). + Equals("backend_group_id", lbbg.Id). + Filter(sqlchemy.OR(sqlchemy.IsNull(pdF), sqlchemy.IsFalse(pdF))). + Count() +} + +func (lbbg *SLoadbalancerBackendGroup) getRefManagers() []db.IModelManager { + // 引用Backend Group的数据库 + return []db.IModelManager{ + LoadbalancerManager, + LoadbalancerListenerManager, + LoadbalancerListenerRuleManager, + } + +} + func (lbbg *SLoadbalancerBackendGroup) AllowPerformStatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return false } func (lbbg *SLoadbalancerBackendGroup) ValidateDeleteCondition(ctx context.Context) error { - men := []db.IModelManager{ - LoadbalancerManager, - LoadbalancerListenerManager, - LoadbalancerListenerRuleManager, - } - lbbgId := lbbg.Id - for _, man := range men { - t := man.TableSpec().Instance() - pdF := t.Field("pending_deleted") - n := t.Query(). - Equals("backend_group_id", lbbgId). - Filter(sqlchemy.OR(sqlchemy.IsNull(pdF), sqlchemy.IsFalse(pdF))). - Count() + men := lbbg.getRefManagers() + for _, m := range men { + n := lbbg.refCount(m) if n > 0 { return fmt.Errorf("backend group %s is still referred to by %d %s", - lbbgId, n, man.KeywordPlural()) + lbbg.Id, n, m.KeywordPlural()) } } @@ -362,7 +389,11 @@ func (man *SLoadbalancerBackendGroupManager) SyncLoadbalancerBackendgroups(ctx c } func (lbbg *SLoadbalancerBackendGroup) constructFieldsFromCloudBackendgroup(lb *SLoadbalancer, extLoadbalancerBackendgroup cloudprovider.ICloudLoadbalancerBackendGroup) { - lbbg.Name = extLoadbalancerBackendgroup.GetName() + // 对于腾讯云,backend group 名字以本地为准 + if lbbg.GetProviderName() != CLOUD_PROVIDER_QCLOUD || len(lbbg.Name) == 0 { + lbbg.Name = extLoadbalancerBackendgroup.GetName() + } + lbbg.Type = extLoadbalancerBackendgroup.GetType() lbbg.Status = extLoadbalancerBackendgroup.GetStatus() } @@ -397,8 +428,10 @@ func (man *SLoadbalancerBackendGroupManager) newFromCloudLoadbalancerBackendgrou lbbg.LoadbalancerId = lb.Id lbbg.ExternalId = extLoadbalancerBackendgroup.GetGlobalId() - lbbg.constructFieldsFromCloudBackendgroup(lb, extLoadbalancerBackendgroup) + lbbg.CloudregionId = lb.CloudregionId + lbbg.ManagerId = lb.ManagerId + lbbg.constructFieldsFromCloudBackendgroup(lb, extLoadbalancerBackendgroup) lbbg.ProjectId = userCred.GetProjectId() if len(projectId) > 0 { lbbg.ProjectId = projectId diff --git a/pkg/compute/models/loadbalancerbackends.go b/pkg/compute/models/loadbalancerbackends.go index 291b30f4b0..4b7bdf33e2 100644 --- a/pkg/compute/models/loadbalancerbackends.go +++ b/pkg/compute/models/loadbalancerbackends.go @@ -251,6 +251,7 @@ func (lbb *SLoadbalancerBackend) StartLoadBalancerBackendDeleteTask(ctx context. func (man *SLoadbalancerBackendManager) getLoadbalancerBackendsByLoadbalancerBackendgroup(loadbalancerBackendgroup *SLoadbalancerBackendGroup) ([]SLoadbalancerBackend, error) { loadbalancerBackends := []SLoadbalancerBackend{} q := man.Query().Equals("backend_group_id", loadbalancerBackendgroup.Id) + q = q.Filter(sqlchemy.OR(sqlchemy.IsNull(q.Field("pending_deleted")), sqlchemy.IsFalse(q.Field("pending_deleted")))) if err := db.FetchModelObjects(man, q, &loadbalancerBackends); err != nil { return nil, err } @@ -298,7 +299,7 @@ func (man *SLoadbalancerBackendManager) SyncLoadbalancerBackends(ctx context.Con syncResult.Delete() } } else { - err = removed[i].Delete(ctx, userCred) + err = removed[i].PendingDelete() if err != nil { syncResult.DeleteError(err) } else { @@ -367,6 +368,9 @@ func (man *SLoadbalancerBackendManager) newFromCloudLoadbalancerBackend(ctx cont lbb.BackendGroupId = loadbalancerBackendgroup.Id lbb.ExternalId = extLoadbalancerBackend.GetGlobalId() + lbb.CloudregionId = loadbalancerBackendgroup.CloudregionId + lbb.ManagerId = loadbalancerBackendgroup.ManagerId + if err := lbb.constructFieldsFromCloudLoadbalancerBackend(extLoadbalancerBackend); err != nil { return nil, err } diff --git a/pkg/compute/models/loadbalancerlistenerrules.go b/pkg/compute/models/loadbalancerlistenerrules.go index 0dae971fac..08084298cd 100644 --- a/pkg/compute/models/loadbalancerlistenerrules.go +++ b/pkg/compute/models/loadbalancerlistenerrules.go @@ -45,6 +45,7 @@ type SLoadbalancerListenerRule struct { Domain string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"optional"` Path string `width:"128" charset:"ascii" nullable:"false" list:"user" create:"optional"` + SLoadbalancerHealthCheck // 目前只有腾讯云HTTP、HTTPS类型的健康检查是和规则绑定的。 SLoadbalancerHTTPRateLimiter } @@ -116,9 +117,17 @@ func (man *SLoadbalancerListenerRuleManager) ValidateCreateData(ctx context.Cont return nil, fmt.Errorf("listener type must be http/https, got %s", listenerType) } { - if backendGroup, ok := backendGroupV.Model.(*SLoadbalancerBackendGroup); ok && backendGroup.LoadbalancerId != listener.LoadbalancerId { + if lbbg, ok := backendGroupV.Model.(*SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != listener.LoadbalancerId { return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", - backendGroup.Name, backendGroup.Id, backendGroup.LoadbalancerId, listener.LoadbalancerId) + lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, listener.LoadbalancerId) + } else { + // 腾讯云backend group只能1v1关联 + if listener.GetProviderName() == CLOUD_PROVIDER_QCLOUD { + count := lbbg.RefCount() + if count > 0 { + return nil, fmt.Errorf("backendgroup already related with other listener/rule") + } + } } } err := loadbalancerListenerRuleCheckUniqueness(ctx, listener, domainV.Value, pathV.Value) @@ -334,6 +343,18 @@ func (lbr *SLoadbalancerListenerRule) constructFieldsFromCloudListenerRule(extRu lbr.Domain = extRule.GetDomain() lbr.Path = extRule.GetPath() if groupId := extRule.GetBackendGroupId(); len(groupId) > 0 { + // 腾讯云兼容代码。主要目的是在关联listener rule时回写一个fake的backend group external id + if len(groupId) > 0 && len(lbr.BackendGroupId) > 0 { + ilbbg, err := LoadbalancerBackendGroupManager.FetchById(lbr.BackendGroupId) + lbbg := ilbbg.(*SLoadbalancerBackendGroup) + if err == nil && (len(lbbg.ExternalId) == 0 || lbbg.ExternalId != groupId) { + err = lbbg.SetExternalId(groupId) + if err != nil { + log.Errorf("Update loadbalancer BackendGroup(%s) external id failed: %s", lbbg.GetId(), err) + } + } + } + if backendgroup, err := LoadbalancerBackendGroupManager.FetchByExternalId(groupId); err == nil { lbr.BackendGroupId = backendgroup.GetId() } diff --git a/pkg/compute/models/loadbalancerlisteners.go b/pkg/compute/models/loadbalancerlisteners.go index 6dc26364f8..10d29cfdef 100644 --- a/pkg/compute/models/loadbalancerlisteners.go +++ b/pkg/compute/models/loadbalancerlisteners.go @@ -42,6 +42,23 @@ type SLoadbalancerHTTPRateLimiter struct { HTTPRequestRatePerSrc int `nullable:"false" list:"user" create:"optional" update:"user"` } +type SLoadbalancerHealthCheck struct { + HealthCheck string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + HealthCheckType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + + HealthCheckDomain string `charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + HealthCheckURI string `charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + HealthCheckHttpCode string `charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` + + HealthCheckRise int `nullable:"false" list:"user" create:"optional" update:"user"` + HealthCheckFall int `nullable:"false" list:"user" create:"optional" update:"user"` + HealthCheckTimeout int `nullable:"false" list:"user" create:"optional" update:"user"` + HealthCheckInterval int `nullable:"false" list:"user" create:"optional" update:"user"` + + HealthCheckReq string `list:"user" create:"optional" update:"user"` + HealthCheckExp string `list:"user" create:"optional" update:"user"` +} + type SLoadbalancerTCPListener struct{} type SLoadbalancerUDPListener struct{} @@ -90,26 +107,12 @@ type SLoadbalancerListener struct { AclType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` AclId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheck string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheckType string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` - - HealthCheckDomain string `charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheckURI string `charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheckHttpCode string `charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"` - - HealthCheckRise int `nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheckFall int `nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheckTimeout int `nullable:"false" list:"user" create:"optional" update:"user"` - HealthCheckInterval int `nullable:"false" list:"user" create:"optional" update:"user"` - - HealthCheckReq string `list:"user" create:"optional" update:"user"` - HealthCheckExp string `list:"user" create:"optional" update:"user"` - SLoadbalancerTCPListener SLoadbalancerUDPListener SLoadbalancerHTTPListener SLoadbalancerHTTPSListener + SLoadbalancerHealthCheck SLoadbalancerHTTPRateLimiter } @@ -218,9 +221,17 @@ func (man *SLoadbalancerListenerManager) ValidateCreateData(ctx context.Context, } } { - if backendGroup, ok := backendGroupV.Model.(*SLoadbalancerBackendGroup); ok && backendGroup.LoadbalancerId != lb.Id { + if lbbg, ok := backendGroupV.Model.(*SLoadbalancerBackendGroup); ok && lbbg.LoadbalancerId != lb.Id { return nil, httperrors.NewInputParameterError("backend group %s(%s) belongs to loadbalancer %s instead of %s", - backendGroup.Name, backendGroup.Id, backendGroup.LoadbalancerId, lb.Id) + lbbg.Name, lbbg.Id, lbbg.LoadbalancerId, lb.Id) + } else { + // 腾讯云backend group只能1v1关联 + if lb.GetProviderName() == CLOUD_PROVIDER_QCLOUD { + count := lbbg.RefCount() + if count > 0 { + return nil, fmt.Errorf("backendgroup aready related with other listener/rule") + } + } } } { @@ -763,6 +774,18 @@ func (lblis *SLoadbalancerListener) constructFieldsFromCloudListener(lb *SLoadba lblis.Gzip = extListener.GzipEnabled() } groupId := extListener.GetBackendGroupId() + // 腾讯云兼容代码。主要目的是在关联listen时回写一个fake的backend group external id + if len(groupId) > 0 && len(lblis.BackendGroupId) > 0 { + ilbbg, err := LoadbalancerBackendGroupManager.FetchById(lblis.BackendGroupId) + lbbg := ilbbg.(*SLoadbalancerBackendGroup) + if err == nil && (len(lbbg.ExternalId) == 0 || lbbg.ExternalId != groupId) { + err = lbbg.SetExternalId(groupId) + if err != nil { + log.Errorf("Update loadbalancer BackendGroup(%s) external id failed: %s", lbbg.GetId(), err) + } + } + } + if len(groupId) == 0 { lblis.BackendGroupId = lb.BackendGroupId } else if group, err := LoadbalancerBackendGroupManager.FetchByExternalId(groupId); err == nil { diff --git a/pkg/compute/models/loadbalancers.go b/pkg/compute/models/loadbalancers.go index be072ab187..7a77c7c984 100644 --- a/pkg/compute/models/loadbalancers.go +++ b/pkg/compute/models/loadbalancers.go @@ -39,6 +39,7 @@ func init() { const ( LB_CHARGE_TYPE_BY_TRAFFIC = "traffic" LB_CHARGE_TYPE_BY_BANDWIDTH = "bandwidth" + LB_CHARGE_TYPE_BY_HOUR = "hour" ) // TODO build errors on pkg/httperrors/errors.go @@ -66,7 +67,8 @@ type SLoadbalancer struct { ChargeType string `list:"user" get:"user" create:"optional"` LoadbalancerSpec string `list:"user" get:"user" create:"optional"` - BackendGroupId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" update:"user"` + BackendGroupId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" update:"user"` + LBInfo jsonutils.JSONObject `charset:"utf8" nullable:"true" list:"user" update:"admin" create:"admin_optional"` } func (man *SLoadbalancerManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { @@ -551,6 +553,10 @@ func (man *SLoadbalancerManager) newFromCloudLoadbalancer(ctx context.Context, u lb.ProjectId = provider.ProjectId } + if extLb.GetMetadata() != nil { + lb.LBInfo = extLb.GetMetadata() + } + if err := man.TableSpec().Insert(&lb); err != nil { log.Errorf("newFromCloudRegion fail %s", err) return nil, err @@ -581,6 +587,10 @@ func (lb *SLoadbalancer) SyncWithCloudLoadbalancer(ctx context.Context, userCred lb.LoadbalancerSpec = extLb.GetLoadbalancerSpec() lb.ChargeType = extLb.GetChargeType() + if extLb.GetMetadata() != nil { + lb.LBInfo = extLb.GetMetadata() + } + if projectSync && len(projectId) > 0 { lb.ProjectId = projectId } diff --git a/pkg/compute/regiondrivers/qcloud.go b/pkg/compute/regiondrivers/qcloud.go index 6aed19e9be..c4617cf423 100644 --- a/pkg/compute/regiondrivers/qcloud.go +++ b/pkg/compute/regiondrivers/qcloud.go @@ -1,7 +1,14 @@ package regiondrivers import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient" ) type SQcloudRegionDriver struct { @@ -16,3 +23,274 @@ func init() { func (self *SQcloudRegionDriver) GetProvider() string { return models.CLOUD_PROVIDER_QCLOUD } + +func (self *SQcloudRegionDriver) RequestCreateLoadbalancerBackendGroup(ctx context.Context, userCred mcclient.TokenCredential, lbbg *models.SLoadbalancerBackendGroup, backends []cloudprovider.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := lbbg.GetIRegion() + if err != nil { + return nil, err + } + + // 腾讯云本身没有后端服务器组,因此不需要在qcloud端执行创建操作 + if iRegion.GetProvider() == models.CLOUD_PROVIDER_QCLOUD { + return nil, nil + } + + loadbalancer := lbbg.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for backendgroup %s", lbbg.Name) + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + group := &cloudprovider.SLoadbalancerBackendGroup{ + Name: lbbg.Name, + GroupType: lbbg.Type, + Backends: backends, + } + iLoadbalancerBackendGroup, err := iLoadbalancer.CreateILoadBalancerBackendGroup(group) + if err != nil { + return nil, err + } + if err := lbbg.SetExternalId(iLoadbalancerBackendGroup.GetGlobalId()); err != nil { + return nil, err + } + iBackends, err := iLoadbalancerBackendGroup.GetILoadbalancerBackends() + if err != nil { + return nil, err + } + if len(iBackends) > 0 { + provider := loadbalancer.GetCloudprovider() + if provider == nil { + return nil, fmt.Errorf("failed to find cloudprovider for lb %s", loadbalancer.Name) + } + models.LoadbalancerBackendManager.SyncLoadbalancerBackends(ctx, userCred, provider, lbbg, iBackends, &models.SSyncRange{}) + } + return nil, nil + }) + return nil +} + +func (self *SQcloudRegionDriver) RequestCreateLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + lbbg := lbb.GetLoadbalancerBackendGroup() + if lbbg == nil { + return nil, fmt.Errorf("failed to find lbbg for backend %s", lbb.Name) + } + + guest := lbb.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find guest for lbb %s", lbb.Name) + } + + // 兼容腾讯云,在fake的backend group 关联具体的转发策略之前。不需要同步后端服务器 + if lbbg.GetProviderName() == models.CLOUD_PROVIDER_QCLOUD && lbbg.RefCount() == 0 { + return nil, nil + } + + lb := lbbg.GetLoadbalancer() + if lb == nil { + return nil, fmt.Errorf("failed to find lb for backendgroup %s", lbbg.Name) + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(lbbg.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackend, err := iLoadbalancerBackendGroup.AddBackendServer(guest.ExternalId, lbb.Weight, lbb.Port) + if err != nil { + return nil, err + } + if err := lbb.SetExternalId(iLoadbalancerBackend.GetGlobalId()); err != nil { + return nil, err + } + return nil, lbb.SyncWithCloudLoadbalancerBackend(ctx, userCred, iLoadbalancerBackend, "", false) + }) + return nil +} + +func (self *SQcloudRegionDriver) RequestDeleteLoadbalancerBackend(ctx context.Context, userCred mcclient.TokenCredential, lbb *models.SLoadbalancerBackend, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + if jsonutils.QueryBoolean(task.GetParams(), "purge", false) { + return nil, nil + } + lbbg := lbb.GetLoadbalancerBackendGroup() + if lbbg == nil { + return nil, fmt.Errorf("failed to find lbbg for backend %s", lbb.Name) + } + lb := lbbg.GetLoadbalancer() + if lb == nil { + return nil, fmt.Errorf("failed to find lb for backendgroup %s", lbbg.Name) + } + iRegion, err := lb.GetIRegion() + if err != nil { + return nil, err + } + + // ===========兼容腾讯云,未关联具体转发规则时,直接删除本地数据即可=============== + if iRegion.GetProvider() == models.CLOUD_PROVIDER_QCLOUD { + count := lbbg.RefCount() + if count == 0 { + return nil, nil + } + } + + iLoadbalancer, err := iRegion.GetILoadBalancerById(lb.ExternalId) + if err != nil { + return nil, err + } + iLoadbalancerBackendGroup, err := iLoadbalancer.GetILoadBalancerBackendGroupById(lbbg.ExternalId) + if err != nil { + return nil, err + } + guest := lbb.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find guest for lbb %s", lbb.Name) + } + return nil, iLoadbalancerBackendGroup.RemoveBackendServer(guest.ExternalId, lbb.Weight, lbb.Port) + }) + return nil +} + +func (self *SQcloudRegionDriver) RequestCreateLoadbalancerListener(ctx context.Context, userCred mcclient.TokenCredential, lblis *models.SLoadbalancerListener, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + params, err := lblis.GetLoadbalancerListenerParams() + if err != nil { + return nil, err + } + loadbalancer := lblis.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for lblis %s", lblis.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.CreateILoadBalancerListener(params) + if err != nil { + return nil, err + } + if err := lblis.SetExternalId(iListener.GetGlobalId()); err != nil { + return nil, err + } + + // ====腾讯云添加后端服务器===== + if iRegion.GetProvider() == models.CLOUD_PROVIDER_QCLOUD { + group := lblis.GetLoadbalancerBackendGroup() + if group != nil { + backends, err := group.GetBackends() + if err != nil { + return nil, fmt.Errorf("failed to find backends for backend group %s: %s", group.GetId(), err) + } + + extBgID := iListener.GetBackendGroupId() + if len(extBgID) == 0 { + return nil, fmt.Errorf("the backend group external id of loadbalancer listener %s is empty", lblis.GetId()) + } + + ilbbg, err := iLoadbalancer.GetILoadBalancerBackendGroupById(extBgID) + if err != nil { + return nil, fmt.Errorf("failed to find backend group for loadbalancer listener %s: %s", lblis.GetId(), err) + } + + for _, backend := range backends { + guest := backend.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find instance for loadbalancer backend %s", backend.GetId()) + } + _, err := ilbbg.AddBackendServer(guest.GetExternalId(), backend.Weight, backend.Port) + if err != nil { + return nil, err + } + } + } + } + + return nil, lblis.SyncWithCloudLoadbalancerListener(ctx, userCred, loadbalancer, iListener, "", false) + }) + return nil +} + +func (self *SQcloudRegionDriver) RequestCreateLoadbalancerListenerRule(ctx context.Context, userCred mcclient.TokenCredential, lbr *models.SLoadbalancerListenerRule, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + listener := lbr.GetLoadbalancerListener() + if listener == nil { + return nil, fmt.Errorf("failed to find listener for listnener rule %s", lbr.Name) + } + loadbalancer := listener.GetLoadbalancer() + if loadbalancer == nil { + return nil, fmt.Errorf("failed to find loadbalancer for listener %s", listener.Name) + } + iRegion, err := loadbalancer.GetIRegion() + if err != nil { + return nil, err + } + iLoadbalancer, err := iRegion.GetILoadBalancerById(loadbalancer.ExternalId) + if err != nil { + return nil, err + } + iListener, err := iLoadbalancer.GetILoadBalancerListenerById(listener.ExternalId) + if err != nil { + return nil, err + } + rule := &cloudprovider.SLoadbalancerListenerRule{ + Name: lbr.Name, + Domain: lbr.Domain, + Path: lbr.Path, + } + if len(lbr.BackendGroupId) > 0 { + group := lbr.GetLoadbalancerBackendGroup() + if group == nil { + return nil, fmt.Errorf("failed to find backend group for listener rule %s", lbr.Name) + } + rule.BackendGroupID = group.ExternalId + rule.BackendGroupType = group.Type + } + iListenerRule, err := iListener.CreateILoadBalancerListenerRule(rule) + if err != nil { + return nil, err + } + if err := lbr.SetExternalId(iListenerRule.GetGlobalId()); err != nil { + return nil, err + } + // ====腾讯云添加后端服务器===== + if listener.GetProviderName() == models.CLOUD_PROVIDER_QCLOUD && len(rule.BackendGroupID) > 0 { + ilbbg, err := iLoadbalancer.GetILoadBalancerBackendGroupById(rule.BackendGroupID) + if err != nil { + return nil, fmt.Errorf("failed to find backend group for listener rule %s: %s", lbr.Name, err) + } + + group := lbr.GetLoadbalancerBackendGroup() + backends, err := group.GetBackends() + if err != nil { + return nil, fmt.Errorf("failed to find backends for backend group %s: %s", group.GetId(), err) + } + + for _, backend := range backends { + guest := backend.GetGuest() + if guest == nil { + return nil, fmt.Errorf("failed to find instance for loadbalancer backend %s", backend.GetId()) + } + _, err := ilbbg.AddBackendServer(guest.GetExternalId(), backend.Weight, backend.Port) + if err != nil { + return nil, err + } + } + + } + return nil, lbr.SyncWithCloudLoadbalancerListenerRule(ctx, userCred, iListenerRule, "", false) + }) + return nil +} diff --git a/pkg/util/aliyun/loadbalancer.go b/pkg/util/aliyun/loadbalancer.go index b1a0b974bd..e65fac4134 100644 --- a/pkg/util/aliyun/loadbalancer.go +++ b/pkg/util/aliyun/loadbalancer.go @@ -237,9 +237,9 @@ func (lb *SLoadbalancer) GetLoadbalancerSpec() string { func (lb *SLoadbalancer) GetChargeType() string { switch lb.InternetChargeType { case "paybybandwidth": - return "bandwidth" + return models.LB_CHARGE_TYPE_BY_BANDWIDTH case "paybytraffic": - return "traffic" + return models.LB_CHARGE_TYPE_BY_TRAFFIC } return "unknown" } diff --git a/pkg/util/qcloud/certificate.go b/pkg/util/qcloud/certificate.go new file mode 100755 index 0000000000..6bccdb553e --- /dev/null +++ b/pkg/util/qcloud/certificate.go @@ -0,0 +1,118 @@ +package qcloud + +import ( + "fmt" + "strconv" + "time" +) + +type projectInfo struct { + ProjectID string `json:"projectId"` + OwnerUin int64 `json:"ownerUin"` + Name string `json:"name"` + CreatorUin int64 `json:"creatorUin"` + CreateTime string `json:"createTime"` + Info string `json:"info"` +} + +// https://cloud.tencent.com/document/api/400/13675 +type SCertificate struct { + OwnerUin string `json:"ownerUin"` + ProjectID string `json:"projectId"` + From string `json:"from"` + Type int `json:"type"` + Cert string `json:"cert"` + CERTType string `json:"certType"` + ProductZhName string `json:"productZhName"` + Domain string `json:"domain"` + Alias string `json:"alias"` + Status int `json:"status"` + VulnerabilityStatus string `json:"vulnerability_status"` + VerifyType string `json:"verifyType"` + CERTBeginTime time.Time `json:"certBeginTime"` + CERTEndTime time.Time `json:"certEndTime"` + ValidityPeriod string `json:"validityPeriod"` + InsertTime string `json:"insertTime"` + ProjectInfo projectInfo `json:"projectInfo"` + ID string `json:"id"` // 证书Id + SubjectAltName []string `json:"subjectAltName"` + TypeName string `json:"type_name"` + StatusName string `json:"status_name"` + IsVip bool `json:"is_vip"` + IsDv bool `json:"is_dv"` + IsWildcard bool `json:"is_wildcard"` + IsVulnerability bool `json:"is_vulnerability"` +} + +func (self *SRegion) GetCertificates(id string, withCert bool, limit int, page int) ([]SCertificate, int, error) { + params := map[string]string{} + if withCert { + params["withCert"] = "1" + } + + if len(id) > 0 { + params["id"] = id + } + + if limit > 0 { + params["count"] = strconv.Itoa(limit) + } + + if page > 0 { + params["page"] = strconv.Itoa(page) + } + + resp, err := self.wssRequest("CertGetList", params) + if err != nil { + return nil, 0, err + } + + certs := []SCertificate{} + err = resp.Unmarshal(&certs, "list") + if err != nil { + return nil, 0, err + } + + total, err := resp.Float("totalNum") + if err != nil { + return nil, 0, err + } + + return certs, int(total), nil +} + +// https://cloud.tencent.com/document/api/400/9078 +// 返回证书ID +func (self *SRegion) CreateCertificate(cert, certType, key, desc string) (string, error) { + + params := map[string]string{ + "cert": cert, + "certType": certType, + "alias": desc, + } + + if certType == "SVR" { + params["key"] = key + } + + resp, err := self.wssRequest("CertUpload", params) + if err != nil { + return "", err + } + + return resp.GetString("id") +} + +func (self *SRegion) DeleteCertificate(id string) error { + if len(id) == 0 { + return fmt.Errorf("DelteCertificate certificate id should not be empty") + } + + params := map[string]string{"id": id} + _, err := self.wssRequest("CertDelete", params) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/util/qcloud/instancenic.go b/pkg/util/qcloud/instancenic.go index f9c71f4d27..5ac0522562 100644 --- a/pkg/util/qcloud/instancenic.go +++ b/pkg/util/qcloud/instancenic.go @@ -25,7 +25,7 @@ func (self *SInstanceNic) GetDriver() string { } func (self *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork { - networkId := self.instance.VirtualPrivateCloud.VpcId + networkId := self.instance.VirtualPrivateCloud.SubnetId wires, err := self.instance.host.GetIWires() if err != nil { return nil diff --git a/pkg/util/qcloud/loadbalancer.go b/pkg/util/qcloud/loadbalancer.go new file mode 100755 index 0000000000..1af91e969d --- /dev/null +++ b/pkg/util/qcloud/loadbalancer.go @@ -0,0 +1,579 @@ +package qcloud + +import ( + "fmt" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" +) + +const ( + LB_ADDR_TYPE_INTERNAL = "INTERNAL" + LB_ADDR_TYPE_OPEN = "OPEN" +) + +type LB_TYPE int64 + +const ( + LB_TYPE_CLASSIC = LB_TYPE(0) + LB_TYPE_APPLICATION = LB_TYPE(1) +) + +/* +todo: +1.统一LB 证书fingerprint算法.另外缺少一个回写指纹算法到数据库的方法。 +2.需要同步腾讯云LB 所在的project +*/ + +// https://cloud.tencent.com/document/api/214/30694#LoadBalancer +type SLoadbalancer struct { + region *SRegion + + Status int64 `json:"Status"` // 0:创建中,1:正常运行 + Domain string `json:"Domain"` + VpcID string `json:"VpcId"` + Log string `json:"Log"` + ProjectID int64 `json:"ProjectId"` + Snat bool `json:"Snat"` + LoadBalancerID string `json:"LoadBalancerId"` + LoadBalancerVips []string `json:"LoadBalancerVips"` + LoadBalancerType string `json:"LoadBalancerType"` // 负载均衡实例的网络类型: OPEN:公网属性, INTERNAL:内网属性。 + LoadBalancerName string `json:"LoadBalancerName"` + Forward LB_TYPE `json:"Forward"` // 应用型负载均衡标识,1:应用型负载均衡,0:传统型的负载均衡。 + StatusTime time.Time `json:"StatusTime"` + OpenBGP int64 `json:"OpenBgp"` // 高防 LB 的标识,1:高防负载均衡 0:非高防负载均衡。 + CreateTime time.Time `json:"CreateTime"` + Isolation int64 `json:"Isolation"` // 0:表示未被隔离,1:表示被隔离。 + SubnetId string `json:"SubnetId"` +} + +func (self *SLoadbalancer) GetLoadbalancerSpec() string { + return "" +} + +func (self *SLoadbalancer) GetChargeType() string { + return models.LB_CHARGE_TYPE_BY_HOUR +} + +// https://cloud.tencent.com/document/product/214/30689 +func (self *SLoadbalancer) Delete() error { + if self.Forward == LB_TYPE_APPLICATION { + _, err := self.region.DeleteLoadbalancer(self.GetId()) + if err != nil { + return err + } + } else { + _, err := self.region.DeleteClassicLoadbalancer(self.GetId()) + if err != nil { + return err + } + } + + return cloudprovider.WaitDeleted(self, 5*time.Second, 60*time.Second) +} + +// 腾讯云loadbalance不支持启用/禁用 +func (self *SLoadbalancer) Start() error { + return cloudprovider.ErrNotSupported +} + +func (self *SLoadbalancer) Stop() error { + return cloudprovider.ErrNotSupported +} + +// 腾讯云无后端服务器组 +// todo: 是否返回一个fake的后端服务器组 +func (self *SLoadbalancer) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (self *SLoadbalancer) GetILoadBalancerBackendGroupById(groupId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + groups, err := self.GetILoadBalancerBackendGroups() + if err != nil { + return nil, err + } + + for _, group := range groups { + if group.GetId() == groupId { + return group, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func onecloudHealthCodeToQcloud(codes string) int { + qcode := 0 + for i, code := range HTTP_CODES { + if strings.Contains(code, codes) { + // 按位或然后再赋值qcode + qcode |= 1 << uint(i) + } + } + + return qcode +} + +// https://cloud.tencent.com/document/product/214/30693 +// todo: 1.限制比较多必须加参数校验 2.Onecloud 不支持双向证书可能存在兼容性问题 +// 应用型负载均衡 https监听默认开启SNI。传统型不支持设置SNI +func (self *SLoadbalancer) CreateILoadBalancerListener(listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + sniSwitch := 0 + if listener.ListenerType == models.LB_LISTENER_TYPE_HTTPS { + sniSwitch = 1 + } + + hc := getHealthCheck(listener) + cert := getCertificate(listener) + + var listenId string + var err error + if self.Forward == LB_TYPE_APPLICATION { + listenId, err = self.region.CreateLoadbalancerListener(self.GetId(), + listener.Name, + getProtocol(listener), + listener.ListenerPort, + getScheduler(listener), + &listener.StickySessionCookieTimeout, + &sniSwitch, + hc, + cert) + } else { + // 传统型内网属性负载均衡不支持指定scheduler + var scheduler *string + if self.LoadBalancerType == "OPEN" { + scheduler = getScheduler(listener) + } + + listenId, err = self.region.CreateClassicLoadbalancerListener(self.GetId(), + listener.Name, + getClassicLBProtocol(listener), + listener.ListenerPort, + listener.BackendServerPort, + scheduler, + &listener.StickySessionCookieTimeout, + &sniSwitch, + hc, + cert) + } + + if err != nil { + return nil, err + } + + time.Sleep(3 * time.Second) + return self.GetILoadBalancerListenerById(listenId) +} + +func (self *SLoadbalancer) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { + listeners, err := self.GetLoadbalancerListeners("") + if err != nil { + return nil, err + } + + for _, listener := range listeners { + if listener.GetId() == listenerId { + return &listener, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SLoadbalancer) GetId() string { + return self.LoadBalancerID +} + +func (self *SLoadbalancer) GetName() string { + return self.LoadBalancerName +} + +// add region? +func (self *SLoadbalancer) GetGlobalId() string { + return self.LoadBalancerID +} + +func (self *SLoadbalancer) GetStatus() string { + switch self.Status { + case 0: + return models.LB_STATUS_INIT + case 1: + return models.LB_STATUS_ENABLED + default: + return models.LB_STATUS_UNKNOWN + } +} + +func (self *SLoadbalancer) Refresh() error { + lb, err := self.region.GetLoadbalancer(self.GetId()) + if err != nil { + return err + } + + return jsonutils.Update(self, lb) +} + +func (self *SLoadbalancer) IsEmulated() bool { + return false +} + +func (self *SLoadbalancer) GetMetadata() *jsonutils.JSONDict { + meta := jsonutils.NewDict() + meta.Add(jsonutils.NewInt(int64(self.Forward)), "Forward") + meta.Add(jsonutils.NewInt(self.OpenBGP), "OpenBGP") + meta.Add(jsonutils.NewString(self.Domain), "Domain") + meta.Add(jsonutils.NewInt(self.ProjectID), "ProjectID") + + return meta +} + +// 腾讯云当前不支持一个LB绑定多个ip,每个LB只支持绑定一个ip +func (self *SLoadbalancer) GetAddress() string { + return self.LoadBalancerVips[0] +} + +func (self *SLoadbalancer) GetAddressType() string { + switch self.LoadBalancerType { + case LB_ADDR_TYPE_INTERNAL: + return models.LB_ADDR_TYPE_INTRANET + case LB_ADDR_TYPE_OPEN: + return models.LB_ADDR_TYPE_INTERNET + default: + return "" + } +} + +func (self *SLoadbalancer) GetNetworkType() string { + return models.LB_NETWORK_TYPE_VPC +} + +func (self *SLoadbalancer) GetNetworkId() string { + return self.SubnetId +} + +func (self *SLoadbalancer) GetVpcId() string { + return self.VpcID +} + +func (self *SLoadbalancer) GetZoneId() string { + return "" +} + +func (self *SLoadbalancer) GetLoadbalancerListeners(protocal string) ([]SLBListener, error) { + listeners, err := self.region.GetLoadbalancerListeners(self.GetId(), self.Forward, protocal) + if err != nil { + return nil, err + } + + for i := range listeners { + listeners[i].lb = self + } + + return listeners, nil +} + +func (self *SLoadbalancer) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { + listeners, err := self.GetLoadbalancerListeners("") + if err != nil { + return nil, err + } + + ilisteners := make([]cloudprovider.ICloudLoadbalancerListener, len(listeners)) + for i := range listeners { + l := listeners[i] + ilisteners[i] = &l + } + + return ilisteners, nil +} + +func (self *SLoadbalancer) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { + listeners, err := self.GetLoadbalancerListeners("") + if err != nil { + return nil, err + } + + bgs := []cloudprovider.ICloudLoadbalancerBackendGroup{} + for i := range listeners { + listener := listeners[i] + t := listener.GetListenerType() + if t == models.LB_LISTENER_TYPE_HTTP || t == models.LB_LISTENER_TYPE_HTTPS { + rules := listener.Rules + for i := range rules { + rule := rules[i] + rule.listener = &listener + bg := rule.GetBackendGroup() + bgs = append(bgs, bg) + } + } else { + bg := listener.GetBackendGroup() + bgs = append(bgs, bg) + } + } + + ibgs := make([]cloudprovider.ICloudLoadbalancerBackendGroup, len(bgs)) + for i := range bgs { + ibgs[i] = bgs[i] + } + + return ibgs, nil +} + +func (self *SRegion) GetLoadbalancers(ids []string) ([]SLoadbalancer, error) { + params := map[string]string{} + for i, id := range ids { + params[fmt.Sprintf("LoadBalancerIds.%d", i)] = id + } + + offset := 0 + limit := 100 + lbs := make([]SLoadbalancer, 0) + for { + params["Limit"] = strconv.Itoa(limit) + params["Offset"] = strconv.Itoa(offset) + + resp, err := self.clbRequest("DescribeLoadBalancers", params) + if err != nil { + return nil, err + } + + parts := make([]SLoadbalancer, 0) + err = resp.Unmarshal(&parts, "LoadBalancerSet") + if err != nil { + return nil, err + } + + _total, err := resp.Float("TotalCount") + if err != nil { + return nil, err + } + + total := int(_total) + if err != nil { + return nil, err + } + + lbs = append(lbs, parts...) + offset += len(parts) + if offset >= total { + for i := range lbs { + lbs[i].region = self + } + + return lbs, err + } + } +} + +func (self *SRegion) GetLoadbalancer(id string) (*SLoadbalancer, error) { + if len(id) == 0 { + return nil, fmt.Errorf("GetLoadbalancer id should not empty") + } + + lbs, err := self.GetLoadbalancers([]string{id}) + if err != nil { + return nil, err + } + + switch len(lbs) { + case 0: + return nil, cloudprovider.ErrNotFound + case 1: + return &lbs[0], nil + default: + return nil, fmt.Errorf("GetLoadbalancer %s found %d", id, len(lbs)) + } +} + +/* +返回requstid 用于异步任务查询 +https://cloud.tencent.com/document/product/214/30689 +*/ +func (self *SRegion) DeleteLoadbalancer(lbid string) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + params := map[string]string{"LoadBalancerIds.0": lbid} + resp, err := self.clbRequest("DeleteLoadBalancer", params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +/* +返回requstid 用于异步任务查询 +https://cloud.tencent.com/document/product/214/30689 +*/ +func (self *SRegion) DeleteClassicLoadbalancer(lbid string) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + params := map[string]string{"loadBalancerIds.n": lbid} + resp, err := self.lbRequest("DeleteLoadBalancers", params) + if err != nil { + return "", err + } + + return resp.GetString("requestId") +} + +/* +https://cloud.tencent.com/document/product/214/30693 +SNI 特性是什么?? +*/ +func (self *SRegion) CreateLoadbalancerListener(lbid, name, protocol string, port int, scheduler *string, sessionExpireTime, sniSwitch *int, healthCheck *healthCheck, cert *certificate) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + params := map[string]string{ + "LoadBalancerId": lbid, + "Ports.0": strconv.Itoa(port), + "Protocol": protocol, + } + + if len(name) > 0 { + params["ListenerNames.0"] = name + } + + if sniSwitch != nil { + params["SniSwitch"] = strconv.Itoa(*sniSwitch) + } + + if sessionExpireTime != nil { + params["SessionExpireTime"] = strconv.Itoa(*sessionExpireTime) + } + + if scheduler != nil && len(*scheduler) > 0 { + params["Scheduler"] = *scheduler + } + + params = healthCheckParams(LB_TYPE_APPLICATION, params, healthCheck, "HealthCheck.") + params = certificateParams(LB_TYPE_APPLICATION, params, cert, "Certificate.") + + resp, err := self.clbRequest("CreateListener", params) + if err != nil { + return "", err + } + + listeners, err := resp.GetArray("ListenerIds") + if err != nil { + return "", err + } + + if len(listeners) == 0 { + return "", fmt.Errorf("CreateLoadbalancerListener no listener id returned: %s", resp.String()) + } else if len(listeners) == 1 { + return listeners[0].GetString() + } else { + return "", fmt.Errorf("CreateLoadbalancerListener mutliple listener id returned: %s", resp.String()) + } +} + +// https://cloud.tencent.com/document/api/214/1255 +// 不支持sniSwitch +// todo: 待测试 +func (self *SRegion) CreateClassicLoadbalancerListener(lbid, name string, protocol, port, backendServerPort int, scheduler *string, sessionExpireTime, sniSwitch *int, healthCheck *healthCheck, cert *certificate) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + // 负载均衡实例监听器协议类型 1:HTTP,2:TCP,3:UDP,4:HTTPS。 + // todo: 待测试 。 这里没有判断是否为公网负载均衡,可能存在问题.内网传统型负载均衡监听协议只支持TCP、UDP,并且不能指定调度算法 + params := map[string]string{ + "loadBalancerId": lbid, + "listeners.0.loadBalancerPort": strconv.Itoa(port), + "listeners.0.instancePort": strconv.Itoa(backendServerPort), + "listeners.0.protocol": strconv.Itoa(protocol), + } + + if len(name) > 0 { + params["listeners.0.listenerName"] = name + } + + if sessionExpireTime != nil { + params["listeners.0.sessionExpire"] = strconv.Itoa(*sessionExpireTime) + } + + if scheduler != nil && len(*scheduler) > 0 && (protocol == 2 || protocol == 3) { + params["listeners.0.scheduler"] = strings.ToLower(*scheduler) + } + + if scheduler != nil && len(*scheduler) > 0 && (protocol == 1 || protocol == 4) { + params["listeners.0.httpHash"] = strings.ToLower(*scheduler) + } + + params = healthCheckParams(LB_TYPE_CLASSIC, params, healthCheck, "listeners.0.") + params = certificateParams(LB_TYPE_CLASSIC, params, cert, "listeners.0.") + + resp, err := self.lbRequest("CreateLoadBalancerListeners", params) + if err != nil { + return "", err + } + + listeners, err := resp.GetArray("listenerIds") + if err != nil { + return "", err + } + + if len(listeners) == 0 { + return "", fmt.Errorf("CreateLoadbalancerListener no listener id returned: %s", resp.String()) + } else if len(listeners) == 1 { + return listeners[0].GetString() + } else { + return "", fmt.Errorf("CreateLoadbalancerListener mutliple listener id returned: %s", resp.String()) + } +} + +// https://cloud.tencent.com/document/product/214/30683 +// 任务的当前状态。 0:成功,1:失败,2:进行中 +func (self *SRegion) GetLBTaskStatus(requestId string) (string, error) { + if len(requestId) == 0 { + return "", fmt.Errorf("WaitTaskSuccess requestId should not be emtpy") + } + + params := map[string]string{"TaskId": requestId} + resp, err := self.clbRequest("DescribeTaskStatus", params) + if err != nil { + return "", err + } + + status, err := resp.Get("Status") + if err != nil { + log.Debugf("WaitTaskSuccess failed %s: %s", err, resp.String()) + return "", err + } + + _status, err := status.Float() + return fmt.Sprintf("%1.f", _status), err +} + +func (self *SRegion) WaitLBTaskSuccess(requestId string, interval time.Duration, timeout time.Duration) error { + startTime := time.Now() + for time.Now().Sub(startTime) < timeout { + status, err := self.GetLBTaskStatus(requestId) + if err != nil { + return err + } + if status == "0" { + return nil + } + + if status == "1" { + return fmt.Errorf("Task %s failed.", requestId) + } + + time.Sleep(interval) + } + + return cloudprovider.ErrTimeout +} diff --git a/pkg/util/qcloud/loadbalancer_acl.go b/pkg/util/qcloud/loadbalancer_acl.go new file mode 100755 index 0000000000..61fe1f7da6 --- /dev/null +++ b/pkg/util/qcloud/loadbalancer_acl.go @@ -0,0 +1,41 @@ +package qcloud + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/compute/models" +) + +// 腾讯云没有LB ACL +type SLBACL struct{} + +func (self *SLBACL) GetId() string { + return "" +} + +func (self *SLBACL) GetName() string { + return "" +} + +func (self *SLBACL) GetGlobalId() string { + return "" +} + +func (self *SLBACL) GetStatus() string { + return models.LB_BOOL_OFF +} + +func (self *SLBACL) Refresh() error { + return nil +} + +func (self *SLBACL) IsEmulated() bool { + return false +} + +func (self *SLBACL) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLBACL) GetAclEntries() *jsonutils.JSONArray { + return nil +} diff --git a/pkg/util/qcloud/loadbalancer_backend.go b/pkg/util/qcloud/loadbalancer_backend.go new file mode 100755 index 0000000000..357f337358 --- /dev/null +++ b/pkg/util/qcloud/loadbalancer_backend.go @@ -0,0 +1,171 @@ +package qcloud + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SLBBackend struct { + group *SLBBackendGroup + + PublicIPAddresses []string `json:"PublicIpAddresses"` + Weight int `json:"Weight"` + InstanceID string `json:"InstanceId"` + InstanceName string `json:"InstanceName"` + PrivateIPAddresses []string `json:"PrivateIpAddresses"` + RegisteredTime string `json:"RegisteredTime"` + Type string `json:"Type"` + Port int `json:"Port"` +} + +// ========================================================== +type SListenerBackend struct { + Rules []rule `json:"Rules"` + Targets []SLBBackend `json:"Targets"` + Protocol string `json:"Protocol"` + ListenerID string `json:"ListenerId"` + Port int64 `json:"Port"` +} + +type rule struct { + URL string `json:"Url"` + Domain string `json:"Domain"` + LocationID string `json:"LocationId"` + Targets []SLBBackend `json:"Targets"` +} + +// ========================================================== + +// backend InstanceID + protocol +Port + ip + rip全局唯一 +func (self *SLBBackend) GetId() string { + return fmt.Sprintf("%s/%s-%d", self.group.GetId(), self.InstanceID, self.Port) +} + +func (self *SLBBackend) GetName() string { + return self.GetId() +} + +func (self *SLBBackend) GetGlobalId() string { + return self.GetId() +} + +func (self *SLBBackend) GetStatus() string { + return "" +} + +func (self *SLBBackend) Refresh() error { + backends, err := self.group.GetBackends() + if err != nil { + return err + } + + for _, backend := range backends { + if backend.GetId() == self.GetId() { + return jsonutils.Update(self, backend) + } + } + + return cloudprovider.ErrNotFound +} + +func (self *SLBBackend) IsEmulated() bool { + return false +} + +func (self *SLBBackend) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLBBackend) GetWeight() int { + return self.Weight +} + +func (self *SLBBackend) GetPort() int { + return self.Port +} + +func (self *SLBBackend) GetBackendType() string { + return models.LB_BACKEND_GUEST +} + +func (self *SLBBackend) GetBackendRole() string { + return models.LB_BACKEND_ROLE_DEFAULT +} + +func (self *SLBBackend) GetBackendId() string { + return self.InstanceID +} + +// 传统型: https://cloud.tencent.com/document/product/214/31790 +func (self *SRegion) getClassicBackends(lbId, listenerId string) ([]SLBBackend, error) { + params := map[string]string{"LoadBalancerId": lbId} + + resp, err := self.clbRequest("DescribeClassicalLBTargets", params) + if err != nil { + return nil, err + } + + backends := []SLBBackend{} + err = resp.Unmarshal(&backends, "Targets") + if err != nil { + return nil, err + } + return backends, nil +} + +// 应用型: https://cloud.tencent.com/document/product/214/30684 +func (self *SRegion) getBackends(lbId, listenerId, ruleId string) ([]SLBBackend, error) { + params := map[string]string{"LoadBalancerId": lbId} + + if len(listenerId) > 0 { + params["ListenerIds.0"] = listenerId + } + + resp, err := self.clbRequest("DescribeTargets", params) + if err != nil { + return nil, err + } + + lbackends := []SListenerBackend{} + err = resp.Unmarshal(&lbackends, "Listeners") + if err != nil { + return nil, err + } + + for _, entry := range lbackends { + if (entry.Protocol == "HTTP" || entry.Protocol == "HTTPS") && len(ruleId) == 0 { + return nil, fmt.Errorf("GetBackends for http/https listener %s must specific rule id", listenerId) + } + + if len(ruleId) > 0 { + for _, r := range entry.Rules { + if r.LocationID == ruleId { + return r.Targets, nil + } + } + } else { + return entry.Targets, nil + } + } + + // todo: 这里是返回空列表还是404? + return []SLBBackend{}, nil +} + +// 注意http、https监听器必须指定ruleId +func (self *SRegion) GetLBBackends(t LB_TYPE, lbId, listenerId, ruleId string) ([]SLBBackend, error) { + if len(lbId) == 0 { + return nil, fmt.Errorf("GetLBBackends loadbalancer id should not be empty") + } + + if t == LB_TYPE_APPLICATION { + return self.getBackends(lbId, listenerId, ruleId) + } else if t == LB_TYPE_CLASSIC { + return self.getClassicBackends(lbId, listenerId) + } else { + return nil, fmt.Errorf("GetLBBackends unsupported loadbalancer type %d", t) + } +} diff --git a/pkg/util/qcloud/loadbalancer_backendgroup.go b/pkg/util/qcloud/loadbalancer_backendgroup.go new file mode 100755 index 0000000000..7ec3f60c0e --- /dev/null +++ b/pkg/util/qcloud/loadbalancer_backendgroup.go @@ -0,0 +1,223 @@ +package qcloud + +import ( + "fmt" + "strconv" + "time" + + "strings" + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SLBBackendGroup struct { + lb *SLoadbalancer // 必须不能为nil + listener *SLBListener // 必须不能为nil + rule *SLBListenerRule // tcp、udp、tcp_ssl监听rule 为nil +} + +// 返回requestid +func (self *SLBBackendGroup) appLBBackendServer(action string, serverId string, weight int, port int) (string, error) { + if len(serverId) == 0 { + return "", fmt.Errorf("loadbalancer backend instance id should not be empty.") + } + + params := map[string]string{ + "LoadBalancerId": self.lb.GetId(), + "ListenerId": self.listener.GetId(), + "Targets.0.InstanceId": serverId, + "Targets.0.Port": strconv.Itoa(port), + "Targets.0.Weight": strconv.Itoa(weight), + } + + if self.rule != nil { + params["LocationId"] = self.rule.GetId() + } + + resp, err := self.lb.region.clbRequest(action, params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +// 返回requestid +func (self *SLBBackendGroup) classicLBBackendServer(action string, serverId string, weight int, port int) (string, error) { + // 传统型负载均衡忽略了port参数 + params := map[string]string{ + "LoadBalancerId": self.lb.GetId(), + "Targets.0.InstanceId": serverId, + "Targets.0.Weight": strconv.Itoa(weight), + } + + resp, err := self.lb.region.clbRequest(action, params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +// https://cloud.tencent.com/document/product/214/30676 +// https://cloud.tencent.com/document/product/214/31789 +func (self *SLBBackendGroup) AddBackendServer(serverId string, weight int, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + var requestId string + var err error + if self.lb.Forward == LB_TYPE_APPLICATION { + requestId, err = self.appLBBackendServer("RegisterTargets", serverId, weight, port) + } else { + requestId, err = self.classicLBBackendServer("RegisterTargetsWithClassicalLB", serverId, weight, port) + } + + if err != nil { + return nil, err + } + + err = self.lb.region.WaitLBTaskSuccess(requestId, 5*time.Second, 60*time.Second) + if err != nil { + return nil, err + } + + err = self.Refresh() + if err != nil { + return nil, err + } + + backends, err := self.GetBackends() + if err != nil { + return nil, err + } + + for _, backend := range backends { + if strings.HasSuffix(backend.GetId(), fmt.Sprintf("%s-%d", serverId, port)) { + return &backend, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +// https://cloud.tencent.com/document/product/214/30687 +// https://cloud.tencent.com/document/product/214/31794 +func (self *SLBBackendGroup) RemoveBackendServer(serverId string, weight int, port int) error { + var requestId string + var err error + if self.lb.Forward == LB_TYPE_APPLICATION { + requestId, err = self.appLBBackendServer("DeregisterTargets", serverId, weight, port) + } else { + requestId, err = self.classicLBBackendServer("DeregisterTargetsFromClassicalLB", serverId, weight, port) + } + + if err != nil { + if strings.Contains(err.Error(), "not registered") { + return nil + } + return err + } + + return self.lb.region.WaitLBTaskSuccess(requestId, 5*time.Second, 60*time.Second) +} + +// 腾讯云无后端服务器组。 +func (self *SLBBackendGroup) Delete() error { + return fmt.Errorf("Please remove related listener/rule frist") +} + +// 腾讯云无后端服务器组 +func (self *SLBBackendGroup) Sync(name string) error { + return nil +} + +func backendGroupIdGen(lbid string, secondId string) string { + if len(secondId) > 0 { + return fmt.Sprintf("%s/%s", lbid, secondId) + } else { + return lbid + } +} + +func (self *SLBBackendGroup) GetId() string { + t := self.listener.GetListenerType() + if t == models.LB_LISTENER_TYPE_HTTP || t == models.LB_LISTENER_TYPE_HTTPS { + // http https 后端服务器只与规则绑定 + return backendGroupIdGen(self.lb.GetId(), self.rule.GetId()) + } else if self.lb.Forward == LB_TYPE_APPLICATION { + return backendGroupIdGen(self.lb.GetId(), self.listener.GetId()) + } else { + // 传统型lb 所有监听共用一个后端服务器组 + return backendGroupIdGen(self.lb.GetId(), "") + } +} + +func (self *SLBBackendGroup) GetName() string { + return self.GetId() +} + +func (self *SLBBackendGroup) GetGlobalId() string { + return self.GetId() +} + +func (self *SLBBackendGroup) GetStatus() string { + return models.LB_STATUS_ENABLED +} + +func (self *SLBBackendGroup) Refresh() error { + return nil +} + +// note: model没有更新这个字段? +func (self *SLBBackendGroup) IsEmulated() bool { + return true +} + +func (self *SLBBackendGroup) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLBBackendGroup) IsDefault() bool { + return false +} + +func (self *SLBBackendGroup) GetType() string { + return models.LB_BACKENDGROUP_TYPE_NORMAL +} + +func (self *SLBBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { + backends, err := self.GetBackends() + if err != nil { + return nil, err + } + + ibackends := make([]cloudprovider.ICloudLoadbalancerBackend, len(backends)) + for i := range backends { + ibackends[i] = &backends[i] + } + + return ibackends, nil +} + +func (self *SLBBackendGroup) GetBackends() ([]SLBBackend, error) { + backends := []SLBBackend{} + var err error + if self.rule != nil { + // http、https监听 + backends, err = self.lb.region.GetLBBackends(self.lb.Forward, self.lb.GetId(), self.listener.GetId(), self.rule.GetId()) + if err != nil { + return nil, err + } + } else { + // tcp,udp,tcp_ssl监听 + backends, err = self.lb.region.GetLBBackends(self.lb.Forward, self.lb.GetId(), self.listener.GetId(), "") + if err != nil { + return nil, err + } + } + + for i := range backends { + backends[i].group = self + } + + return backends, nil +} diff --git a/pkg/util/qcloud/loadbalancer_certificate.go b/pkg/util/qcloud/loadbalancer_certificate.go new file mode 100755 index 0000000000..1ec5df50c1 --- /dev/null +++ b/pkg/util/qcloud/loadbalancer_certificate.go @@ -0,0 +1,83 @@ +package qcloud + +import ( + "crypto/sha1" + "fmt" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" +) + +type SLBCertificate struct { + region *SRegion + + SCertificate +} + +// 证书不能修改 +func (self *SLBCertificate) Sync(name, privateKey, publickKey string) error { + return nil +} + +func (self *SLBCertificate) Delete() error { + return self.region.DeleteCertificate(self.GetId()) +} + +func (self *SLBCertificate) GetId() string { + return self.ID +} + +func (self *SLBCertificate) GetName() string { + return self.Alias +} + +func (self *SLBCertificate) GetGlobalId() string { + return self.ID +} + +// todo: 貌似目前onecloud没有记录状态 +func (self *SLBCertificate) GetStatus() string { + return strconv.Itoa(self.Status) +} + +func (self *SLBCertificate) Refresh() error { + certs, total, err := self.region.GetCertificates(self.GetId(), true, 0, 0) + if err != nil { + return err + } + + if total != 1 { + return fmt.Errorf("Expecting 1 certificate, got %d", total) + } + + cert := SLBCertificate{region: self.region, SCertificate: certs[0]} + return jsonutils.Update(self, cert) +} + +func (self *SLBCertificate) IsEmulated() bool { + return false +} + +func (self *SLBCertificate) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLBCertificate) GetCommonName() string { + return self.Domain +} + +func (self *SLBCertificate) GetSubjectAlternativeNames() string { + return strings.Join(self.SubjectAltName, ",") +} + +func (self *SLBCertificate) GetFingerprint() string { + _fp := sha1.Sum([]byte(self.Cert)) + fp := fmt.Sprintf("sha1:% x", _fp) + return strings.Replace(fp, " ", ":", -1) +} + +func (self *SLBCertificate) GetExpireTime() time.Time { + return self.CERTEndTime +} diff --git a/pkg/util/qcloud/loadbalancer_listener.go b/pkg/util/qcloud/loadbalancer_listener.go new file mode 100755 index 0000000000..7d38492210 --- /dev/null +++ b/pkg/util/qcloud/loadbalancer_listener.go @@ -0,0 +1,782 @@ +package qcloud + +import ( + "fmt" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" +) + +var HTTP_CODES = []string{ + models.LB_HEALTH_CHECK_HTTP_CODE_1xx, + models.LB_HEALTH_CHECK_HTTP_CODE_2xx, + models.LB_HEALTH_CHECK_HTTP_CODE_3xx, + models.LB_HEALTH_CHECK_HTTP_CODE_4xx, + models.LB_HEALTH_CHECK_HTTP_CODE_5xx, +} + +type certificate struct { + SSLMode string `json:"SSLMode"` + CERTCAID string `json:"CertCaId"` + CERTID string `json:"CertId"` +} + +/* +健康检查状态码(仅适用于HTTP/HTTPS转发规则)。可选值:1~31,默认 31。 +1 表示探测后返回值 1xx 表示健康,2 表示返回 2xx 表示健康,4 表示返回 3xx 表示健康,8 表示返回 4xx 表示健康,16 表示返回 5xx 表示健康。 +若希望多种码都表示健康,则将相应的值相加。 +*/ +type healthCheck struct { + HTTPCheckDomain string `json:"HttpCheckDomain"` + HealthSwitch int `json:"HealthSwitch"` + HTTPCheckPath string `json:"HttpCheckPath"` + HTTPCheckMethod string `json:"HttpCheckMethod"` + UnHealthNum int `json:"UnHealthNum"` + IntervalTime int `json:"IntervalTime"` + HTTPCode int `json:"HttpCode"` // 健康检查状态码(仅适用于HTTP/HTTPS转发规则)。可选值:1~31,默认 31。 + HealthNum int `json:"HealthNum"` + TimeOut int `json:"TimeOut"` +} + +type SLBListener struct { + lb *SLoadbalancer + + Protocol string `json:"Protocol"` // 监听器协议类型,取值 TCP | UDP | HTTP | HTTPS | TCP_SSL + Certificate certificate `json:"Certificate"` + SniSwitch int64 `json:"SniSwitch"` // 是否开启SNI特性(本参数仅对于HTTPS监听器有意义) + HealthCheck healthCheck `json:"HealthCheck"` // 仅适用于TCP/UDP/TCP_SSL监听器 + ListenerID string `json:"ListenerId"` + ListenerName string `json:"ListenerName"` + Rules []SLBListenerRule `json:"Rules"` // 监听器下的全部转发规则(本参数仅对于HTTP/HTTPS监听器有意义) + Scheduler string `json:"Scheduler"` + SessionExpireTime int `json:"SessionExpireTime"` // 会话保持时间,单位:秒。可选值:30~3600,默认 0,表示不开启。此参数仅适用于TCP/UDP监听器。 + Port int `json:"Port"` + ClassicListener bool // 这个字段是在qcloud返回字段基础上,额外增加的字段。用于区分listener 是否是classic。 +} + +// 腾讯云后端端口不是与listener绑定的 +func (self *SLBListener) GetBackendServerPort() int { + return 0 +} + +// https://cloud.tencent.com/document/product/214/30691 +func (self *SLBListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + requestId, err := self.lb.region.CreateLoadbalancerListenerRule(self.lb.GetId(), + self.GetId(), + rule.Domain, + rule.Path, + &self.Scheduler, + &self.SessionExpireTime) + if err != nil { + return nil, err + } + + err = self.lb.region.WaitLBTaskSuccess(requestId, 5*time.Second, 60*time.Second) + if err != nil { + return nil, err + } + + err = self.Refresh() + if err != nil { + return nil, err + } + + for _, r := range self.Rules { + if r.GetPath() == rule.Path { + return &r, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SLBListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + rules, err := self.GetILoadbalancerListenerRules() + if err != nil { + return nil, err + } + + for _, rule := range rules { + if rule.GetId() == ruleId { + return rule, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SLBListener) Start() error { + return nil +} + +func (self *SLBListener) Stop() error { + return cloudprovider.ErrNotSupported +} + +// https://cloud.tencent.com/document/product/214/30677 +func (self *SLBListener) Sync(listener *cloudprovider.SLoadbalancerListener) error { + hc := getHealthCheck(listener) + cert := getCertificate(listener) + requestId, err := self.lb.region.UpdateLoadbalancerListener( + self.lb.Forward, + self.lb.GetId(), + self.GetId(), + &listener.Name, + getScheduler(listener), + &listener.StickySessionCookieTimeout, + hc, + cert) + if err != nil { + return err + } + + return self.lb.region.WaitLBTaskSuccess(requestId, 5*time.Second, 60*time.Second) +} + +func (self *SLBListener) Delete() error { + requestId, err := self.lb.region.DeleteLoadbalancerListener(self.lb.Forward, self.lb.GetId(), self.GetId()) + if err != nil { + return err + } + + return self.lb.region.WaitLBTaskSuccess(requestId, 5*time.Second, 60*time.Second) +} + +// https://cloud.tencent.com/document/api/214/30694#ClassicalListener +type SLBClassicListener struct { + InstancePort int64 `json:"InstancePort"` + CERTCAID string `json:"CertCaId"` + Status int64 `json:"Status"` + CERTID string `json:"CertId"` + Protocol string `json:"Protocol"` + TimeOut int `json:"TimeOut"` + HTTPHash string `json:"HttpHash"` // 公网固定IP型的 HTTP、HTTPS 协议监听器的轮询方法。wrr 表示按权重轮询,ip_hash 表示根据访问的源 IP 进行一致性哈希方式来分发 + UnhealthNum int `json:"UnhealthNum"` + IntervalTime int `json:"IntervalTime"` + ListenerID string `json:"ListenerId"` + ListenerPort int `json:"ListenerPort"` + HTTPCheckPath string `json:"HttpCheckPath"` + HealthNum int `json:"HealthNum"` + ListenerName string `json:"ListenerName"` + HealthSwitch int `json:"HealthSwitch"` + SSLMode string `json:"SSLMode"` + SessionExpire int `json:"SessionExpire"` + HTTPCode int `json:"HttpCode"` +} + +func (self *SLBClassicListener) ToLBListener() SLBListener { + // 转换之后丢弃了 InstancePort、Status、HttpHash + return SLBListener{ + Protocol: self.Protocol, + Certificate: certificate{ + SSLMode: self.SSLMode, + CERTCAID: self.CERTCAID, + CERTID: self.CERTID, + }, + HealthCheck: healthCheck{ + HTTPCheckDomain: "", + HealthSwitch: self.HealthSwitch, + HTTPCheckPath: self.HTTPCheckPath, + HTTPCheckMethod: "", + UnHealthNum: self.UnhealthNum, + IntervalTime: self.IntervalTime, + HTTPCode: self.HTTPCode, + HealthNum: self.HealthNum, + TimeOut: self.TimeOut, + }, + ListenerID: self.ListenerID, + ListenerName: self.ListenerName, + Rules: nil, + Scheduler: self.HTTPHash, + SessionExpireTime: self.SessionExpire, + Port: self.ListenerPort, + ClassicListener: true, + } +} + +func (self *SLBListener) GetId() string { + return self.ListenerID +} + +func (self *SLBListener) GetName() string { + return self.ListenerName +} + +func (self *SLBListener) GetGlobalId() string { + return self.ListenerID +} + +// 腾讯云负载均衡没有启用禁用操作 +func (self *SLBListener) GetStatus() string { + return models.LB_STATUS_ENABLED +} + +func (self *SLBListener) Refresh() error { + listeners, err := self.lb.region.GetLoadbalancerListeners(self.lb.GetId(), self.lb.Forward, "") + if err != nil { + return err + } + + for _, listener := range listeners { + if listener.GetId() == self.GetId() { + listener.lb = self.lb + err := jsonutils.Update(self, listener) + if err != nil { + return err + } + } + } + + return cloudprovider.ErrNotFound +} + +func (self *SLBListener) IsEmulated() bool { + return false +} + +func (self *SLBListener) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLBListener) GetListenerType() string { + switch self.Protocol { + case "TCP": + return models.LB_LISTENER_TYPE_TCP + case "UDP": + return models.LB_LISTENER_TYPE_UDP + case "HTTP": + return models.LB_LISTENER_TYPE_HTTP + case "HTTPS": + return models.LB_LISTENER_TYPE_HTTPS + case "TCP_SSL": + return models.LB_LISTENER_TYPE_TCP + default: + return "" + } +} + +func (self *SLBListener) GetListenerPort() int { + return self.Port +} + +func (self *SLBListener) GetScheduler() string { + switch strings.ToLower(self.Scheduler) { + case "wrr": + return models.LB_SCHEDULER_WRR + case "ip_hash": + return models.LB_SCHEDULER_SCH + case "least_conn": + return models.LB_SCHEDULER_WLC + default: + return "" + } +} + +func (self *SLBListener) GetAclStatus() string { + return models.LB_BOOL_OFF +} + +func (self *SLBListener) GetAclType() string { + return "" +} + +func (self *SLBListener) GetAclId() string { + return "" +} + +func (self *SLBListener) GetHealthCheck() string { + if self.HealthCheck.HealthSwitch == 0 { + return models.LB_BOOL_OFF + } else { + return models.LB_BOOL_ON + } +} + +func (self *SLBListener) GetHealthCheckType() string { + if len(self.HealthCheck.HTTPCheckMethod) > 0 { + return models.LB_HEALTH_CHECK_HTTP + } else { + return models.LB_HEALTH_CHECK_TCP + } +} + +func (self *SLBListener) GetHealthCheckTimeout() int { + return self.HealthCheck.TimeOut +} + +func (self *SLBListener) GetHealthCheckInterval() int { + return self.HealthCheck.IntervalTime +} + +func (self *SLBListener) GetHealthCheckRise() int { + return self.HealthCheck.HealthNum +} + +func (self *SLBListener) GetHealthCheckFail() int { + return self.HealthCheck.UnHealthNum +} + +func (self *SLBListener) GetHealthCheckReq() string { + return "" +} + +func (self *SLBListener) GetHealthCheckExp() string { + return "" +} + +func (self *SLBListener) GetBackendGroup() *SLBBackendGroup { + t := self.GetListenerType() + // http、https类型的监听不能直接绑定服务器 + if t == models.LB_LISTENER_TYPE_HTTP || t == models.LB_LISTENER_TYPE_HTTPS { + return nil + } else { + return &SLBBackendGroup{lb: self.lb, listener: self} + } +} + +func (self *SLBListener) GetBackendGroupId() string { + bg := self.GetBackendGroup() + if bg == nil { + return "" + } + + return bg.GetId() +} + +func (self *SLBListener) GetHealthCheckDomain() string { + return self.HealthCheck.HTTPCheckDomain +} + +func (self *SLBListener) GetHealthCheckURI() string { + return self.HealthCheck.HTTPCheckPath +} + +func (self *SLBListener) GetHealthCheckCode() string { + codes := []string{} + for i := uint8(0); i < 5; i++ { + n := 1 << i + if (self.HealthCheck.HTTPCode & n) == n { + codes = append(codes, HTTP_CODES[i]) + } + } + + return strings.Join(codes, ",") +} + +// 仅http、https类型监听包含rules +func (self *SLBListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + rules := self.Rules + iRules := []cloudprovider.ICloudLoadbalancerListenerRule{} + for i := 0; i < len(rules); i++ { + rules[i].listener = self + iRules = append(iRules, &rules[i]) + } + return iRules, nil +} + +func (self *SLBListener) GetStickySession() string { + if self.SessionExpireTime == 0 { + return models.LB_BOOL_OFF + } else { + return models.LB_BOOL_ON + } +} + +// 支持基于 cookie 插入的会话保持能力 https://cloud.tencent.com/document/product/214/6154 +func (self *SLBListener) GetStickySessionType() string { + return models.LB_STICKY_SESSION_TYPE_INSERT +} + +// https://cloud.tencent.com/document/product/214/2736 +// 经测试应用型负载均衡返回都是 tgw_l7_route。 +func (self *SLBListener) GetStickySessionCookie() string { + if self.GetListenerType() == models.LB_LISTENER_TYPE_HTTPS { + return "tgw_l7_route" + } + + return "" +} + +func (self *SLBListener) GetStickySessionCookieTimeout() int { + return self.SessionExpireTime +} + +/* +7层负载均衡系统提供 X-Forwarded-For 的方式获取访问者真实 IP,LB 侧默认开启 + +https://cloud.tencent.com/document/product/214/6151 +七层转发获取来访真实IP的方法 https://cloud.tencent.com/document/product/214/3728 +*/ +func (self *SLBListener) XForwardedForEnabled() bool { + switch self.GetListenerType() { + case models.LB_LISTENER_TYPE_HTTP, models.LB_LISTENER_TYPE_HTTPS: + return true + default: + return false + } +} + +// HTTP/HTTPS协议默认支持用户开启gzip压缩功能 +// 负载均衡开启Gzip配置及检测方法说明 https://cloud.tencent.com/document/product/214/5404 +func (self *SLBListener) GzipEnabled() bool { + switch self.GetListenerType() { + case models.LB_LISTENER_TYPE_HTTP, models.LB_LISTENER_TYPE_HTTPS: + return true + default: + return false + } +} + +func (self *SLBListener) GetCertificateId() string { + return self.Certificate.CERTID +} + +// https://cloud.tencent.com/document/product/214/5412#2.-https.E6.94.AF.E6.8C.81.E5.93.AA.E4.BA.9B.E7.89.88.E6.9C.AC.E7.9A.84ssl.2Ftls.E5.AE.89.E5.85.A8.E5.8D.8F.E8.AE.AE.EF.BC.9F +func (self *SLBListener) GetTLSCipherPolicy() string { + return "" +} + +// 负载均衡能力说明 https://cloud.tencent.com/document/product/214/6534 +func (self *SLBListener) HTTP2Enabled() bool { + return true +} + +func (self *SRegion) GetLoadbalancerListeners(lbid string, t LB_TYPE, protocol string) ([]SLBListener, error) { + params := map[string]string{"LoadBalancerId": lbid} + if len(protocol) > 0 { + params["Protocol"] = protocol + } + + listeners := []SLBListener{} + if t == LB_TYPE_CLASSIC { + resp, err := self.clbRequest("DescribeClassicalLBListeners", params) + if err != nil { + return nil, err + } + + clisteners := []SLBClassicListener{} + err = resp.Unmarshal(&clisteners, "Listeners") + if err != nil { + return nil, err + } + + for _, l := range clisteners { + listeners = append(listeners, l.ToLBListener()) + } + + return listeners, nil + } + + resp, err := self.clbRequest("DescribeListeners", params) + if err != nil { + return nil, err + } + + err = resp.Unmarshal(&listeners, "Listeners") + if err != nil { + return nil, err + } + + return listeners, nil +} + +// 返回requestID +func (self *SRegion) CreateLoadbalancerListenerRule(lbid string, listenerId string, domain string, url string, scheduler *string, sessionExpireTime *int) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + params := map[string]string{ + "LoadBalancerId": lbid, + "ListenerId": listenerId, + "Rules.0.Domain": domain, + "Rules.0.Url": url, + } + + if scheduler != nil && len(*scheduler) > 0 { + params["Rules.0.Scheduler"] = *scheduler + } + + if sessionExpireTime != nil { + params["Rules.0.SessionExpireTime"] = strconv.Itoa(*sessionExpireTime) + } + + resp, err := self.clbRequest("CreateRule", params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +// 返回requestID +func (self *SRegion) deleteLoadbalancerListener(lbid string, listenerId string) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + params := map[string]string{ + "LoadBalancerId": lbid, + "ListenerId": listenerId, + } + + resp, err := self.clbRequest("DeleteListener", params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +// 返回requestID +func (self *SRegion) deleteClassicLoadbalancerListener(lbid string, listenerId string) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("classic loadbalancer id should not be empty") + } + + params := map[string]string{ + "loadBalancerId": lbid, + "listenerIds.0": listenerId, + } + + resp, err := self.lbRequest("DeleteLoadBalancerListeners", params) + if err != nil { + return "", err + } + + _requestId, err := resp.Float("requestId") + if err != nil { + return "", err + } + + return fmt.Sprintf("%.f", _requestId), nil +} + +// 返回requestID +func (self *SRegion) DeleteLoadbalancerListener(t LB_TYPE, lbid string, listenerId string) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + if t == LB_TYPE_APPLICATION { + return self.deleteLoadbalancerListener(lbid, listenerId) + } else { + return self.deleteClassicLoadbalancerListener(lbid, listenerId) + } +} + +// https://cloud.tencent.com/document/product/214/30681 +func (self *SRegion) updateLoadbalancerListener(lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *healthCheck, cert *certificate) (string, error) { + params := map[string]string{ + "LoadBalancerId": lbid, + "ListenerId": listenerId, + } + + if listenerName != nil && len(*listenerName) > 0 { + params["ListenerName"] = *listenerName + } + + if scheduler != nil && len(*scheduler) > 0 { + params["Scheduler"] = *scheduler + } + + if sessionExpireTime != nil { + params["SessionExpireTime"] = strconv.Itoa(*sessionExpireTime) + } + + params = healthCheckParams(LB_TYPE_APPLICATION, params, healthCheck, "HealthCheck.") + params = certificateParams(LB_TYPE_APPLICATION, params, cert, "Certificate.") + + resp, err := self.clbRequest("ModifyListener", params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +// https://cloud.tencent.com/document/api/214/3601 +func (self *SRegion) updateClassicLoadbalancerListener(lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *healthCheck, cert *certificate) (string, error) { + params := map[string]string{ + "loadBalancerId": lbid, + "listenerId": listenerId, + } + + if listenerName != nil && len(*listenerName) > 0 { + params["listenerName"] = *listenerName + } + + if scheduler != nil && len(*scheduler) > 0 { + params["scheduler"] = strings.ToLower(*scheduler) + } + + if sessionExpireTime != nil { + params["sessionExpire"] = strconv.Itoa(*sessionExpireTime) + } + + params = healthCheckParams(LB_TYPE_APPLICATION, params, healthCheck, "listeners.0.") + params = certificateParams(LB_TYPE_APPLICATION, params, cert, "listeners.0.") + + resp, err := self.lbRequest("ModifyLoadBalancerListener", params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} + +func (self *SRegion) UpdateLoadbalancerListener(t LB_TYPE, lbid string, listenerId string, listenerName *string, scheduler *string, sessionExpireTime *int, healthCheck *healthCheck, cert *certificate) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("loadbalancer id should not be empty") + } + + if len(listenerId) == 0 { + return "", fmt.Errorf("loadbalancer listener id should not be empty") + } + + if t == LB_TYPE_APPLICATION { + return self.updateLoadbalancerListener(lbid, listenerId, listenerName, scheduler, sessionExpireTime, healthCheck, cert) + } else { + return self.updateClassicLoadbalancerListener(lbid, listenerId, listenerName, scheduler, sessionExpireTime, healthCheck, cert) + } +} + +func getHealthCheck(listener *cloudprovider.SLoadbalancerListener) *healthCheck { + var hc *healthCheck + if listener.HealthCheck == models.LB_BOOL_ON { + hc = &healthCheck{ + HealthSwitch: 1, + UnHealthNum: listener.HealthCheckFail, + IntervalTime: listener.HealthCheckInterval, + HealthNum: listener.HealthCheckRise, + TimeOut: listener.HealthCheckTimeout, + } + + httpCode := onecloudHealthCodeToQcloud(listener.HealthCheckHttpCode) + if httpCode > 0 { + hc.HTTPCode = httpCode + hc.HTTPCheckMethod = "HEAD" // todo: add column HttpCheckMethod in model + hc.HTTPCheckDomain = listener.HealthCheckDomain + hc.HTTPCheckPath = listener.HealthCheckURI + } + } + + return hc +} + +func getCertificate(listener *cloudprovider.SLoadbalancerListener) *certificate { + var cert *certificate + if len(listener.CertificateID) > 0 { + cert = &certificate{ + SSLMode: "UNIDIRECTIONAL", + CERTCAID: listener.CertificateID, + CERTID: "", + } + } + + return cert +} + +func getProtocol(listener *cloudprovider.SLoadbalancerListener) string { + switch listener.ListenerType { + case models.LB_LISTENER_TYPE_HTTPS: + return "HTTPS" + case models.LB_LISTENER_TYPE_HTTP: + return "HTTP" + case models.LB_LISTENER_TYPE_TCP: + return "TCP" + case models.LB_LISTENER_TYPE_UDP: + return "UDP" + case "tcp_ssl": + return "TCP_SSL" + default: + return "" + } +} + +func getClassicLBProtocol(listener *cloudprovider.SLoadbalancerListener) int { + switch listener.ListenerType { + case models.LB_LISTENER_TYPE_HTTP: + return 1 + case models.LB_LISTENER_TYPE_HTTPS: + return 4 + case models.LB_LISTENER_TYPE_TCP: + return 2 + case models.LB_LISTENER_TYPE_UDP: + return 3 + default: + return 0 // 非法值 + } +} + +func getScheduler(listener *cloudprovider.SLoadbalancerListener) *string { + var sch string + switch listener.Scheduler { + case models.LB_SCHEDULER_WRR: + sch = "WRR" + case models.LB_SCHEDULER_WLC: + sch = "LEAST_CONN" + case models.LB_SCHEDULER_SCH: + sch = "IP_HASH" + default: + return nil + } + + return &sch +} + +func healthCheckParams(t LB_TYPE, params map[string]string, hc *healthCheck, paramPrefix string) map[string]string { + if hc == nil { + return params + } + + if t == LB_TYPE_APPLICATION { + params[paramPrefix+"HealthSwitch"] = strconv.Itoa(hc.HealthSwitch) + params[paramPrefix+"TimeOut"] = strconv.Itoa(hc.TimeOut) + params[paramPrefix+"IntervalTime"] = strconv.Itoa(hc.IntervalTime) + params[paramPrefix+"HealthNum"] = strconv.Itoa(hc.HealthNum) + params[paramPrefix+"UnHealthNum"] = strconv.Itoa(hc.UnHealthNum) + if hc.HTTPCode > 0 { + params[paramPrefix+"HttpCode"] = strconv.Itoa(hc.HTTPCode) + params[paramPrefix+"HttpCheckPath"] = hc.HTTPCheckPath + params[paramPrefix+"HttpCheckDomain"] = hc.HTTPCheckDomain + params[paramPrefix+"HttpCheckMethod"] = hc.HTTPCheckMethod + } + } else { + params[paramPrefix+"healthSwitch"] = strconv.Itoa(hc.HealthSwitch) + params[paramPrefix+"timeOut"] = strconv.Itoa(hc.TimeOut) + params[paramPrefix+"intervalTime"] = strconv.Itoa(hc.IntervalTime) + params[paramPrefix+"healthNum"] = strconv.Itoa(hc.HealthNum) + params[paramPrefix+"unHealthNum"] = strconv.Itoa(hc.UnHealthNum) + if hc.HTTPCode > 0 { + params[paramPrefix+"httpCode"] = strconv.Itoa(hc.HTTPCode) + params[paramPrefix+"httpCheckPath"] = hc.HTTPCheckPath + } + } + return params +} + +func certificateParams(t LB_TYPE, params map[string]string, cert *certificate, paramPrefix string) map[string]string { + if cert == nil { + return params + } + + if t == LB_TYPE_APPLICATION { + params[paramPrefix+"SSLMode"] = cert.SSLMode + params[paramPrefix+"CertId"] = cert.CERTID + if len(cert.CERTCAID) > 0 { + params[paramPrefix+"CertCaId"] = cert.CERTCAID + } + } else { + params[paramPrefix+"SSLMode"] = strings.ToLower(cert.SSLMode) + params[paramPrefix+"certId"] = cert.CERTID + if len(cert.CERTCAID) > 0 { + params[paramPrefix+"certCaId"] = cert.CERTCAID + } + } + return params +} diff --git a/pkg/util/qcloud/loadbalancer_listenerrule.go b/pkg/util/qcloud/loadbalancer_listenerrule.go new file mode 100755 index 0000000000..069c6abea3 --- /dev/null +++ b/pkg/util/qcloud/loadbalancer_listenerrule.go @@ -0,0 +1,138 @@ +package qcloud + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SLBListenerRule struct { + listener *SLBListener + + Domain string `json:"Domain"` + Certificate certificate `json:"Certificate"` + URL string `json:"Url"` + HealthCheck healthCheck `json:"HealthCheck"` + LocationID string `json:"LocationId"` + Scheduler string `json:"Scheduler"` + SessionExpireTime int64 `json:"SessionExpireTime"` +} + +// https://cloud.tencent.com/document/api/214/30688 +func (self *SLBListenerRule) Delete() error { + _, err := self.listener.lb.region.DeleteLBListenerRule(self.listener.lb.GetId(), self.listener.GetId(), self.GetId()) + if err != nil { + return err + } + + return cloudprovider.WaitDeleted(self, 5*time.Second, 60*time.Second) +} + +func (self *SLBListenerRule) GetId() string { + return self.LocationID +} + +func (self *SLBListenerRule) GetName() string { + return self.LocationID +} + +func (self *SLBListenerRule) GetGlobalId() string { + return self.LocationID +} + +func (self *SLBListenerRule) GetStatus() string { + return "" +} + +func (self *SLBListenerRule) Refresh() error { + err := self.listener.Refresh() + if err != nil { + return err + } + + for _, rule := range self.listener.Rules { + if rule.GetId() == self.GetId() { + rule.listener = self.listener + return jsonutils.Update(self, rule) + } + } + + return cloudprovider.ErrNotFound +} + +func (self *SLBListenerRule) IsEmulated() bool { + return false +} + +func (self *SLBListenerRule) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SLBListenerRule) GetDomain() string { + return self.Domain +} + +func (self *SLBListenerRule) GetPath() string { + return self.URL +} + +func (self *SLBListenerRule) GetBackendGroup() *SLBBackendGroup { + t := self.listener.GetListenerType() + if t == models.LB_LISTENER_TYPE_HTTP || t == models.LB_LISTENER_TYPE_HTTPS { + return &SLBBackendGroup{ + lb: self.listener.lb, + listener: self.listener, + rule: self, + } + } + + return nil +} + +// 只有http、https协议监听规则有backendgroupid +func (self *SLBListenerRule) GetBackendGroupId() string { + bg := self.GetBackendGroup() + if bg == nil { + return "" + } + + return bg.GetId() +} + +// https://cloud.tencent.com/document/api/214/30688 +// 返回requestId及error +func (self *SRegion) DeleteLBListenerRule(lbid, listenerId, ruleId string) (string, error) { + if len(ruleId) == 0 { + return "", fmt.Errorf("DeleteLBListenerRule rule id should not be empty") + } + return self.DeleteLBListenerRules(lbid, listenerId, []string{ruleId}) +} + +func (self *SRegion) DeleteLBListenerRules(lbid, listenerId string, ruleIds []string) (string, error) { + if len(lbid) == 0 { + return "", fmt.Errorf("DeleteLBListenerRules loadbalancer id should not be empty") + } + + if len(listenerId) == 0 { + return "", fmt.Errorf("DeleteLBListenerRules listener id should not be empty") + } + + if len(ruleIds) == 0 { + return "", fmt.Errorf("DeleteLBListenerRules rule id should not be empty") + } + + params := map[string]string{"LoadBalancerId": lbid, "ListenerId": listenerId} + for i, ruleId := range ruleIds { + params[fmt.Sprintf("LocationIds.%d", i)] = ruleId + } + + resp, err := self.clbRequest("DeleteRule", params) + if err != nil { + return "", err + } + + return resp.GetString("RequestId") +} diff --git a/pkg/util/qcloud/qcloud.go b/pkg/util/qcloud/qcloud.go index f50a3638d4..5695735f55 100644 --- a/pkg/util/qcloud/qcloud.go +++ b/pkg/util/qcloud/qcloud.go @@ -1,11 +1,13 @@ package qcloud import ( + "encoding/json" "fmt" "strings" "time" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" @@ -23,6 +25,7 @@ const ( QCLOUD_DEFAULT_REGION = "ap-beijing" QCLOUD_API_VERSION = "2017-03-12" + QCLOUD_CLB_API_VERSION = "2018-03-17" QCLOUD_BILLING_API_VERSION = "2018-07-09" ) @@ -48,40 +51,153 @@ func NewQcloudClient(providerId string, providerName string, secretID string, se return &client, nil } -func jsonRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { - domain := "cvm.tencentcloudapi.com" - if region, ok := params["Region"]; ok && strings.HasSuffix(region, "-fsi") { - domain = "cvm." + region + ".tencentcloudapi.com" +// 默认接口请求频率限制:20次/秒 +// 部分接口支持金融区地域。由于金融区和非金融区是隔离不互通的,因此当公共参数 Region 为金融区地域(例如 ap-shanghai-fsi)时,需要同时指定带金融区地域的域名,最好和 Region 的地域保持一致,例如:clb.ap-shanghai-fsi.tencentcloudapi.com +// https://cloud.tencent.com/document/product/416/6479 +func apiDomain(product string, params map[string]string) string { + region, ok := params["Region"] + if ok && strings.HasSuffix(region, "-fsi") { + return product + "." + region + ".tencentcloudapi.com" + } else { + return product + ".tencentcloudapi.com" } +} + +func jsonRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { + domain := apiDomain("cvm", params) return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params) } func vpcRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { - domain := "vpc.tencentcloudapi.com" - if region, ok := params["Region"]; ok && strings.HasSuffix(region, "-fsi") { - domain = "vpc." + region + ".tencentcloudapi.com" - } + domain := apiDomain("vpc", params) return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params) } func cbsRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { - domain := "cbs.tencentcloudapi.com" - if region, ok := params["Region"]; ok && strings.HasSuffix(region, "-fsi") { - domain = "cbs." + region + ".tencentcloudapi.com" - } + domain := apiDomain("cbs", params) return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params) } +// loadbalancer服务 api 3.0 +func clbRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { + domain := apiDomain("clb", params) + return _jsonRequest(client, domain, QCLOUD_CLB_API_VERSION, apiName, params) +} + +// loadbalancer服务 api 2017 +func lbRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { + domain := "lb.api.qcloud.com" + return _phpJsonRequest(client, &lbJsonResponse{}, domain, "/v2/index.php", "", apiName, params) +} + +// ssl 证书服务 +func wssRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { + domain := "wss.api.qcloud.com" + return _phpJsonRequest(client, &wssJsonResponse{}, domain, "/v2/index.php", "", apiName, params) +} + func billingRequest(client *common.Client, apiName string, params map[string]string) (jsonutils.JSONObject, error) { domain := "billing.tencentcloudapi.com" return _jsonRequest(client, domain, QCLOUD_BILLING_API_VERSION, apiName, params) } +// ============phpJsonRequest============ +type qcloudResponse interface { + tchttp.Response + GetResponse() *interface{} +} + +type phpJsonRequest struct { + tchttp.BaseRequest + Path string +} + +func (r *phpJsonRequest) GetUrl() string { + url := r.BaseRequest.GetUrl() + if url == "" { + return url + } + + index := strings.Index(url, "?") + if index == -1 { + // POST request + return strings.TrimSuffix(url, "/") + r.Path + } + + p1, p2 := url[:index], url[index:] + p1 = strings.TrimSuffix(p1, "/") + return p1 + r.Path + p2 +} + +func (r *phpJsonRequest) GetPath() string { + return r.Path +} + +// SSL证书专用response +type wssJsonResponse struct { + Code int `json:"code"` + CodeDesc string `json:"codeDesc"` + Message string `json:"message"` + Response *interface{} `json:"data"` +} + +func (r *wssJsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) { + resp := &wssJsonResponse{} + err = json.Unmarshal(body, resp) + if err != nil { + return + } + if resp.Code != 0 { + return errors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "") + } + + return nil +} + +func (r *wssJsonResponse) GetResponse() *interface{} { + return r.Response +} + +// 2017版负载均衡API专用response +type lbJsonResponse struct { + Response map[string]interface{} +} + +func (r *lbJsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) { + resp := &wssJsonResponse{} + err = json.Unmarshal(body, resp) + if err != nil { + return + } + if resp.Code != 0 { + return errors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "") + } + + // hook 由于目前只能从这个方法中拿到原始的body.这里将原始body hook 到 Response + err = json.Unmarshal(body, &r.Response) + if err != nil { + return + } + + return nil +} + +func (r *lbJsonResponse) GetResponse() *interface{} { + return func(resp interface{}) *interface{} { + return &resp + }(r.Response) +} + +// 3.0版本通用response type QcloudResponse struct { *tchttp.BaseResponse Response *interface{} `json:"Response"` } +func (r *QcloudResponse) GetResponse() *interface{} { + return r.Response +} + func _jsonRequest(client *common.Client, domain string, version string, apiName string, params map[string]string) (jsonutils.JSONObject, error) { req := &tchttp.BaseRequest{} if region, ok := params["Region"]; ok { @@ -98,9 +214,36 @@ func _jsonRequest(client *common.Client, domain string, version string, apiName } req.GetParams()[k] = v } + resp := &QcloudResponse{ BaseResponse: &tchttp.BaseResponse{}, } + return _baseJsonRequest(client, req, resp) +} + +// 老版本腾讯云api。 适用于类似 https://cvm.api.qcloud.com/v2/index.php 这样的带/v2/index.php路径的接口 +// todo: 添加自定义response参数 +func _phpJsonRequest(client *common.Client, resp qcloudResponse, domain string, path string, version string, apiName string, params map[string]string) (jsonutils.JSONObject, error) { + req := &phpJsonRequest{Path: path} + if region, ok := params["Region"]; ok { + client = client.Init(region) + } + client.WithProfile(profile.NewClientProfile()) + service := strings.Split(domain, ".")[0] + req.Init().WithApiInfo(service, version, apiName) + req.SetDomain(domain) + + for k, v := range params { + if strings.HasSuffix(k, "Ids.0") && len(v) == 0 { + return nil, cloudprovider.ErrNotFound + } + req.GetParams()[k] = v + } + + return _baseJsonRequest(client, req, resp) +} + +func _baseJsonRequest(client *common.Client, req tchttp.Request, resp qcloudResponse) (jsonutils.JSONObject, error) { for i := 1; i <= 3; i++ { err := client.Send(req, resp) if err == nil { @@ -118,10 +261,10 @@ func _jsonRequest(client *common.Client, domain string, version string, apiName time.Sleep(time.Second * time.Duration(i*10)) continue } - log.Errorf("request url: %s\nparams: %s\nresponse: %s\nerror: %v", req.GetDomain(), jsonutils.Marshal(req.GetParams()).PrettyString(), resp.Response, err) + log.Errorf("request url: %s\nparams: %s\nresponse: %s\nerror: %v", req.GetDomain(), jsonutils.Marshal(req.GetParams()).PrettyString(), resp.GetResponse(), err) return nil, err } - return jsonutils.Marshal(resp.Response), nil + return jsonutils.Marshal(resp.GetResponse()), nil } func (client *SQcloudClient) GetRegions() []SRegion { @@ -153,6 +296,30 @@ func (client *SQcloudClient) cbsRequest(apiName string, params map[string]string return cbsRequest(cli, apiName, params) } +func (client *SQcloudClient) clbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return clbRequest(cli, apiName, params) +} + +func (client *SQcloudClient) lbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return lbRequest(cli, apiName, params) +} + +func (client *SQcloudClient) wssRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + return wssRequest(cli, apiName, params) +} + func (client *SQcloudClient) billingRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { cli, err := client.getDefaultClient() if err != nil { diff --git a/pkg/util/qcloud/region.go b/pkg/util/qcloud/region.go index d7be925eed..d3464cadb2 100644 --- a/pkg/util/qcloud/region.go +++ b/pkg/util/qcloud/region.go @@ -2,6 +2,7 @@ package qcloud import ( "fmt" + "time" "github.com/nelsonken/cos-go-sdk-v5/cos" @@ -33,6 +34,173 @@ type SRegion struct { fetchLocation bool } +func (self *SRegion) GetSkus(zoneId string) ([]cloudprovider.ICloudSku, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, error) { + lbs, err := self.GetLoadbalancers(nil) + if err != nil { + return nil, err + } + + ilbs := make([]cloudprovider.ICloudLoadbalancer, len(lbs)) + for i := range lbs { + lbs[i].region = self + ilbs[i] = &lbs[i] + } + + return ilbs, nil +} + +// 腾讯云不支持acl +func (self *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { + return []cloudprovider.ICloudLoadbalancerAcl{}, nil +} + +func (self *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { + lbs, err := self.GetLoadbalancers(nil) + if err != nil { + return nil, err + } + + icerts := []cloudprovider.ICloudLoadbalancerCertificate{} + for _, lb := range lbs { + listeners, err := lb.GetLoadbalancerListeners("HTTPS") + if err != nil { + return nil, err + } + + certIds := []string{} + for _, listener := range listeners { + if len(listener.Certificate.CERTID) > 0 && !utils.IsInStringArray(listener.Certificate.CERTID, certIds) { + certIds = append(certIds, listener.Certificate.CERTID) + } + + if len(listener.Certificate.CERTCAID) > 0 && !utils.IsInStringArray(listener.Certificate.CERTCAID, certIds) { + certIds = append(certIds, listener.Certificate.CERTCAID) + } + + for _, rule := range listener.Rules { + if len(rule.Certificate.CERTID) > 0 && !utils.IsInStringArray(rule.Certificate.CERTID, certIds) { + certIds = append(certIds, rule.Certificate.CERTID) + } + + if len(rule.Certificate.CERTCAID) > 0 && !utils.IsInStringArray(rule.Certificate.CERTCAID, certIds) { + certIds = append(certIds, rule.Certificate.CERTCAID) + } + } + } + + for _, cid := range certIds { + icert, err := self.GetILoadBalancerCertificateById(cid) + if err != nil { + return nil, err + } + + icerts = append(icerts, icert) + } + } + + return icerts, nil +} + +func (self *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { + certs, _, err := self.GetCertificates(certId, true, 0, 0) + if err != nil { + return nil, err + } + + icerts := []cloudprovider.ICloudLoadbalancerCertificate{} + for i := 0; i < len(certs); i++ { + cert := SLBCertificate{region: self, SCertificate: certs[i]} + icerts = append(icerts, &cert) + } + + if len(certs) == 1 { + return icerts[0], nil + } else { + return nil, fmt.Errorf("GetILoadBalancerCertificateById %d certificate found, expect 1", len(certs)) + } +} + +func (self *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { + lbs, err := self.GetLoadbalancers([]string{loadbalancerId}) + if err != nil { + return nil, err + } + + if len(lbs) == 1 { + lbs[0].region = self + return &lbs[0], nil + } else { + return nil, fmt.Errorf("GetILoadBalancerById %d loadbalancer found", len(lbs)) + } +} + +func (self *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, nil +} + +// https://cloud.tencent.com/document/api/214/30692 +// todo: 1. 支持跨地域绑定负载均衡 及 https://cloud.tencent.com/document/product/214/12014 +// todo: 2. 支持指定Project。 ProjectId可以通过 DescribeProject 接口获取。不填则属于默认项目。 +func (self *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { + LoadBalancerType := "INTERNAL" + if loadbalancer.AddressType == "public" { + LoadBalancerType = "OPEN" + } + params := map[string]string{ + "LoadBalancerType": LoadBalancerType, + "LoadBalancerName": loadbalancer.Name, + "VpcId": loadbalancer.VpcID, + "SubnetId": loadbalancer.NetworkID, + } + + resp, err := self.clbRequest("CreateLoadBalancer", params) + if err != nil { + return nil, err + } + + requestId, err := resp.GetString("RequestId") + if err != nil { + return nil, err + } + + lbs, err := resp.GetArray("LoadBalancerIds") + if err != nil || len(lbs) != 1 { + log.Debugf("CreateILoadBalancer %s", resp.String()) + return nil, err + } + + err = self.WaitLBTaskSuccess(requestId, 5*time.Second, 60*time.Second) + if err != nil { + return nil, err + } + + return self.GetLoadbalancer(lbs[0].String()) +} + +func (self *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { + return nil, cloudprovider.ErrNotSupported +} + +// todo:目前onecloud端只能指定服务器端证书。需要兼容客户端证书? +func (self *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { + certId, err := self.CreateCertificate(cert.Certificate, "SVR", cert.PrivateKey, cert.Name) + if err != nil { + return nil, err + } + + certs, _, err := self.GetCertificates(certId, false, 10, 0) + if len(certs) != 1 || err != nil { + log.Debugf("CreateILoadBalancerCertificate failed. %d certificate matched", len(certs)) + return nil, err + } + + return &SLBCertificate{region: self, SCertificate: certs[0]}, nil +} + func (self *SRegion) GetId() string { return self.Region } @@ -439,6 +607,20 @@ func (self *SRegion) cbsRequest(apiName string, params map[string]string) (jsonu return self.client.cbsRequest(apiName, params) } +func (self *SRegion) clbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.clbRequest(apiName, params) +} + +func (self *SRegion) lbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.lbRequest(apiName, params) +} + +func (self *SRegion) wssRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + return self.client.wssRequest(apiName, params) +} + func (self *SRegion) GetNetworks(ids []string, vpcId string, offset int, limit int) ([]SNetwork, int, error) { if limit > 50 || limit <= 0 { limit = 50 @@ -591,43 +773,3 @@ func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) { } return instance.InstanceState, nil } - -func (region *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { - return nil, cloudprovider.ErrNotImplemented -} - -func (region *SRegion) GetSkus(zoneId string) ([]cloudprovider.ICloudSku, error) { - return nil, cloudprovider.ErrNotImplemented -}