diff --git a/cmd/cloud-server/service/load-balancer/update.go b/cmd/cloud-server/service/load-balancer/update.go index a82282d8d..bc06e12b4 100644 --- a/cmd/cloud-server/service/load-balancer/update.go +++ b/cmd/cloud-server/service/load-balancer/update.go @@ -137,6 +137,7 @@ func (svc *lbSvc) batchUpdateTCloudTargetGroup(cts *rest.Contexts, id string) (i if err := req.Validate(); err != nil { return nil, errf.NewFromErr(errf.InvalidParameter, err) } + // TODO 检查是因为什么原因需要传递 多个安全组id,不需要的话去掉,改成单个 req.IDs = append(req.IDs, id) // 检查目标组是否已绑定RS,如已绑定则不能更新region、vpc @@ -159,7 +160,7 @@ func (svc *lbSvc) batchUpdateTCloudTargetGroup(cts *rest.Contexts, id string) (i Port: req.Port, Weight: req.Weight, } - err = svc.client.DataService().TCloud.LoadBalancer.BatchUpdateTCloudTargetGroup(cts.Kit, dbReq) + err = svc.client.DataService().TCloud.LoadBalancer.UpdateTargetGroup(cts.Kit, dbReq) if err != nil { logs.Errorf("update tcloud target group failed, req: %+v, err: %v, rid: %s", req, err, cts.Kit.Rid) return nil, err @@ -226,7 +227,7 @@ func (svc *lbSvc) updateTCloudTargetGroupHealthCheck(cts *rest.Contexts, tgID st HealthCheck: req.HealthCheck, } - err := svc.client.DataService().TCloud.LoadBalancer.BatchUpdateTCloudTargetGroup(cts.Kit, dbReq) + err := svc.client.DataService().TCloud.LoadBalancer.UpdateTargetGroup(cts.Kit, dbReq) if err != nil { logs.Errorf("update db tcloud target group failed, err: %v, req: %+v, rid: %s", dbReq, err, cts.Kit.Rid) return nil, err diff --git a/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go b/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go index 3377d9816..0a1215c5d 100644 --- a/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go +++ b/cmd/cloud-server/service/sync/tcloud/sync_all_resource.go @@ -102,6 +102,7 @@ func SyncAllResource(kt *kit.Kit, cliSet *client.ClientSet, enumor.LoadBalancerCloudResType: SyncLoadBalancer, enumor.RouteTableCloudResType: SyncRouteTable, enumor.SubAccountCloudResType: SyncSubAccount, + enumor.TargetGroupCloudResType: SyncTargetGroup, } for _, resType := range getSyncOrder() { @@ -123,6 +124,7 @@ func getSyncOrder() []enumor.CloudResourceType { enumor.SecurityGroupCloudResType, enumor.CvmCloudResType, enumor.CertCloudResType, + enumor.TargetGroupCloudResType, enumor.LoadBalancerCloudResType, enumor.RouteTableCloudResType, enumor.SubAccountCloudResType, diff --git a/cmd/cloud-server/service/sync/tcloud/target_group.go b/cmd/cloud-server/service/sync/tcloud/target_group.go new file mode 100644 index 000000000..6baa24066 --- /dev/null +++ b/cmd/cloud-server/service/sync/tcloud/target_group.go @@ -0,0 +1,70 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "time" + + "hcm/cmd/cloud-server/service/sync/detail" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/client" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" +) + +// SyncTargetGroup 同步云上目标组 +func SyncTargetGroup(kt *kit.Kit, cliSet *client.ClientSet, accountID string, regions []string, + sd *detail.SyncDetail) error { + + // 重新设置rid方便定位 + kt = kt.NewSubKit() + + start := time.Now() + logs.V(3).Infof("tcloud account[%s] sync target group start, time: %v, rid: %s", accountID, start, kt.Rid) + + // 同步详情同步中 + if err := sd.ResSyncStatusSyncing(enumor.TargetGroupCloudResType); err != nil { + return err + } + + defer func() { + logs.V(3).Infof("tcloud account[%s] sync target group end, cost: %v, rid: %s", + accountID, time.Since(start), kt.Rid) + }() + + for _, region := range regions { + req := &sync.TCloudSyncReq{ + AccountID: accountID, + Region: region, + } + if err := cliSet.HCService().TCloud.Clb.SyncTargetGroup(kt, req); err != nil { + logs.Errorf("sync tcloud target group failed, err: %v, req: %v, rid: %s", err, req, kt.Rid) + return err + } + } + + // 同步详情同步成功 + if err := sd.ResSyncStatusSuccess(enumor.TargetGroupCloudResType); err != nil { + return err + } + + return nil +} diff --git a/cmd/data-service/service/cloud/load-balancer/create.go b/cmd/data-service/service/cloud/load-balancer/create.go index 3025e2c89..a032481dc 100644 --- a/cmd/data-service/service/cloud/load-balancer/create.go +++ b/cmd/data-service/service/cloud/load-balancer/create.go @@ -233,6 +233,7 @@ func convTargetGroupCreateReqToTable[T corelb.TargetGroupExtension](kt *kit.Kit, } targetGroup := &tablelb.LoadBalancerTargetGroupTable{ + CloudID: tg.CloudID, Name: tg.Name, Vendor: vendor, AccountID: tg.AccountID, @@ -293,18 +294,22 @@ func (svc *lbSvc) batchCreateTargetWithGroupID(kt *kit.Kit, txn *sqlx.Tx, accoun for _, item := range rsList { tmpRs := &tablelb.LoadBalancerTargetTable{ - AccountID: accountID, - InstType: item.InstType, - CloudInstID: item.CloudInstID, - TargetGroupID: item.TargetGroupID, - // for local target group its cloud id is same as local id - CloudTargetGroupID: item.TargetGroupID, + AccountID: accountID, + InstType: item.InstType, + CloudInstID: item.CloudInstID, + TargetGroupID: item.TargetGroupID, + CloudTargetGroupID: item.CloudTargetGroupID, Port: item.Port, Weight: item.Weight, Memo: nil, Creator: kt.User, Reviser: kt.User, } + // for local target group its cloud id is same as local id + if len(item.CloudTargetGroupID) == 0 { + tmpRs.CloudTargetGroupID = item.TargetGroupID + + } // 实例类型-CVM if item.InstType == enumor.CvmInstType { tmpRs.InstID = cvmMap[item.CloudInstID].ID diff --git a/cmd/data-service/service/cloud/load-balancer/load_balancer.go b/cmd/data-service/service/cloud/load-balancer/load_balancer.go index 63c0f17b6..767f524b7 100644 --- a/cmd/data-service/service/cloud/load-balancer/load_balancer.go +++ b/cmd/data-service/service/cloud/load-balancer/load_balancer.go @@ -32,12 +32,8 @@ var svc *lbSvc // InitService initial the clb service func InitService(cap *capability.Capability) { - svc = &lbSvc{ - dao: cap.Dao, - } - + svc = &lbSvc{dao: cap.Dao} h := rest.NewHandler() - // 负载均衡 h.Add("GetLoadBalancer", http.MethodGet, "/vendors/{vendor}/load_balancers/{id}", svc.GetLoadBalancer) h.Add("ListLoadBalancer", http.MethodPost, "/load_balancers/list", svc.ListLoadBalancer) @@ -55,8 +51,8 @@ func InitService(cap *capability.Capability) { h.Add("ListListener", http.MethodPost, "/load_balancers/listeners/list", svc.ListListener) h.Add("ListListenerExt", http.MethodPost, "/vendors/tcloud/load_balancers/listeners/list", svc.ListListenerExt) h.Add("BatchCreateListener", http.MethodPost, "/vendors/{vendor}/listeners/batch/create", svc.BatchCreateListener) - h.Add("BatchCreateListenerWithRule", http.MethodPost, "/vendors/{vendor}/listeners/rules/batch/create", - svc.BatchCreateListenerWithRule) + h.Add("BatchCreateListenerWithRule", http.MethodPost, + "/vendors/{vendor}/listeners/rules/batch/create", svc.BatchCreateListenerWithRule) h.Add("BatchUpdateListener", http.MethodPatch, "/vendors/{vendor}/listeners/batch/update", svc.BatchUpdateListener) h.Add("BatchDeleteListener", http.MethodDelete, "/listeners/batch", svc.BatchDeleteListener) h.Add("CountListenerByLbIDs", http.MethodPost, "/load_balancers/listeners/count", svc.CountListenerByLbIDs) @@ -79,7 +75,11 @@ func InitService(cap *capability.Capability) { "/vendors/{vendor}/target_groups/with/rels/batch/create", svc.BatchCreateTargetGroupWithRel) h.Add("GetTargetGroup", http.MethodGet, "/vendors/{vendor}/target_groups/{id}", svc.GetTargetGroup) h.Add("ListTargetGroup", http.MethodPost, "/load_balancers/target_groups/list", svc.ListTargetGroup) + h.Add("ListTargetGroup", http.MethodPost, + "/vendors/{vendor}/load_balancers/target_groups/list", svc.ListTargetGroupExt) h.Add("UpdateTargetGroup", http.MethodPatch, "/vendors/{vendor}/target_groups", svc.UpdateTargetGroup) + h.Add("BatchUpdateTargetGroup", http.MethodPatch, + "/vendors/{vendor}/target_groups/batch", svc.BatchUpdateTargetGroup) h.Add("BatchDeleteTargetGroup", http.MethodDelete, "/target_groups/batch", svc.BatchDeleteTargetGroup) h.Add("BatchUpdateListenerBizInfo", http.MethodPatch, "/load_balancers/target_groups/bizs/batch/update", svc.BatchUpdateTargetGroupBizInfo) diff --git a/cmd/data-service/service/cloud/load-balancer/query.go b/cmd/data-service/service/cloud/load-balancer/query.go index db57907a0..272cd8b0e 100644 --- a/cmd/data-service/service/cloud/load-balancer/query.go +++ b/cmd/data-service/service/cloud/load-balancer/query.go @@ -539,8 +539,9 @@ func (svc *lbSvc) ListTargetGroup(cts *rest.Contexts) (interface{}, error) { details := make([]corelb.BaseTargetGroup, 0, len(result.Details)) for _, one := range result.Details { - tmpOne, err := convTableToBaseTargetGroup(cts.Kit, &one) + tmpOne, err := convTableToBaseTargetGroup(&one) if err != nil { + logs.Errorf("fail convert db model to base target group, err: %v, rid: %s", err, cts.Kit.Rid) continue } details = append(details, *tmpOne) @@ -549,6 +550,50 @@ func (svc *lbSvc) ListTargetGroup(cts *rest.Contexts) (interface{}, error) { return &protocloud.TargetGroupListResult{Details: details}, nil } +// ListTargetGroupExt list with vendor extension +func (svc *lbSvc) ListTargetGroupExt(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + req := new(core.ListReq) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + vendorFilter, err := tools.And(req.Filter, tools.RuleEqual("vendor", vendor)) + if err != nil { + return nil, err + } + opt := &types.ListOption{ + Fields: req.Fields, + Filter: vendorFilter, + Page: req.Page, + } + + result, err := svc.dao.LoadBalancerTargetGroup().List(cts.Kit, opt) + if err != nil { + logs.Errorf("list target group extension failed, req: %v, err: %v, rid: %s", req, err, cts.Kit.Rid) + return nil, fmt.Errorf("failed to list target group, err: %v", err) + } + + switch vendor { + case enumor.TCloud: + + details, err := convTableToTargetGroup(result.Details) + if err != nil { + return nil, err + } + return core.ListResultT[*corelb.TargetGroup[corelb.TCloudTargetGroupExtension]]{Details: details}, nil + default: + return nil, fmt.Errorf("unsupport vendor: %s", vendor) + } +} + // GetTargetGroup ... func (svc *lbSvc) GetTargetGroup(cts *rest.Contexts) (any, error) { vendor := enumor.Vendor(cts.PathParameter("vendor").String()) @@ -578,22 +623,45 @@ func (svc *lbSvc) GetTargetGroup(cts *rest.Contexts) (any, error) { tgInfo := result.Details[0] switch tgInfo.Vendor { case enumor.TCloud: - return convTableToBaseTargetGroup(cts.Kit, &tgInfo) + list, err := convTableToTargetGroup(result.Details) + if err != nil { + return nil, err + } + return list[0], nil default: return nil, fmt.Errorf("unsupport vendor: %s", vendor) } + +} +func convTableToTargetGroup[T corelb.TargetGroupExtension](models []tablelb.LoadBalancerTargetGroupTable) ( + []*corelb.TargetGroup[T], error) { + + result := make([]*corelb.TargetGroup[T], 0, len(models)) + for _, model := range models { + baseGroup, err := convTableToBaseTargetGroup(cvt.ValToPtr(model)) + if err != nil { + logs.Errorf("fail to convert base target group to json, err: %v", err) + return nil, err + } + var ext T + + if err := json.UnmarshalFromString(string(model.Extension), &ext); err != nil { + return nil, fmt.Errorf("fail to unmarshal target group extension, err: %v", err) + } + result = append(result, &corelb.TargetGroup[T]{BaseTargetGroup: *baseGroup, Extension: &ext}) + + } + return result, nil } -func convTableToBaseTargetGroup(kt *kit.Kit, one *tablelb.LoadBalancerTargetGroupTable) ( - *corelb.BaseTargetGroup, error) { +func convTableToBaseTargetGroup(one *tablelb.LoadBalancerTargetGroupTable) (*corelb.BaseTargetGroup, error) { var healthCheck *corelb.TCloudHealthCheckInfo // 支持不返回该字段 if len(one.HealthCheck) != 0 { err := json.UnmarshalFromString(string(one.HealthCheck), &healthCheck) if err != nil { - logs.Errorf("unmarshal healthCheck failed, one: %+v, err: %v, rid: %s", one, err, kt.Rid) - return nil, err + return nil, fmt.Errorf("unmarshal healthCheck failed: %w", err) } } diff --git a/cmd/data-service/service/cloud/load-balancer/update.go b/cmd/data-service/service/cloud/load-balancer/update.go index 1c92a10ea..e522a70a6 100644 --- a/cmd/data-service/service/cloud/load-balancer/update.go +++ b/cmd/data-service/service/cloud/load-balancer/update.go @@ -32,6 +32,7 @@ import ( "hcm/pkg/dal/dao/orm" "hcm/pkg/dal/dao/tools" "hcm/pkg/dal/dao/types" + "hcm/pkg/dal/table/cloud" tablelb "hcm/pkg/dal/table/cloud/load-balancer" tabletype "hcm/pkg/dal/table/types" "hcm/pkg/kit" @@ -56,7 +57,7 @@ func (svc *lbSvc) BatchUpdateLoadBalancer(cts *rest.Contexts) (any, error) { return batchUpdateLoadBalancer[corelb.TCloudClbExtension](cts, svc) default: - return nil, fmt.Errorf("unsupport vendor %s", vendor) + return nil, fmt.Errorf("unsupport vendor %s", vendor) } } @@ -205,7 +206,7 @@ func (svc *lbSvc) BatchUpdateListenerBizInfo(cts *rest.Contexts) (any, error) { return nil, svc.dao.LoadBalancerListener().Update(cts.Kit, updateFilter, updateField) } -// UpdateTargetGroup batch update argument template +// UpdateTargetGroup update target group TODO: 干掉该接口 func (svc *lbSvc) UpdateTargetGroup(cts *rest.Contexts) (interface{}, error) { req := new(dataproto.TargetGroupUpdateReq) if err := cts.DecodeInto(req); err != nil { @@ -275,6 +276,98 @@ func (svc *lbSvc) UpdateTargetGroup(cts *rest.Contexts) (interface{}, error) { return nil, nil } +// BatchUpdateTargetGroup batch update target group +func (svc *lbSvc) BatchUpdateTargetGroup(cts *rest.Contexts) (any, error) { + vendor := enumor.Vendor(cts.PathParameter("vendor").String()) + if err := vendor.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + switch vendor { + case enumor.TCloud: + return batchUpdateTargetGroup[corelb.TCloudTargetGroupExtension](cts, svc) + + default: + return nil, fmt.Errorf("unsupport vendor %s", vendor) + } +} +func batchUpdateTargetGroup[T corelb.TargetGroupExtension](cts *rest.Contexts, svc *lbSvc) (any, error) { + + req := new(dataproto.TargetGroupBatchUpdateReq[T]) + if err := cts.DecodeInto(req); err != nil { + return nil, err + } + + if err := req.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + updateMap := make(map[string]*dataproto.TargetGroupExtUpdateReq[T], len(req.TargetGroups)) + cloudVpcIDs := make([]string, 0, len(req.TargetGroups)) + for _, group := range req.TargetGroups { + updateMap[group.ID] = group + if len(group.CloudVpcID) > 0 { + cloudVpcIDs = append(cloudVpcIDs, group.CloudVpcID) + } + } + + ids := converter.MapKeyToSlice(updateMap) + tgReq := &types.ListOption{ + Filter: tools.ContainersExpression("id", ids), + Page: &core.BasePage{Limit: uint(len(ids))}, + } + tgList, err := svc.dao.LoadBalancerTargetGroup().List(cts.Kit, tgReq) + if err != nil { + return nil, err + } + if len(tgList.Details) != len(ids) { + return nil, errors.New("not all target groups can be found") + } + + var vpcInfoMap = map[string]cloud.VpcTable{} + vpcInfoMap, err = getVpcMapByIDs(cts.Kit, cloudVpcIDs) + if err != nil { + return nil, err + } + + updateDataList := make([]*tablelb.LoadBalancerTargetGroupTable, 0, len(ids)) + for _, oldTg := range tgList.Details { + newTg := updateMap[oldTg.ID] + updateData := &tablelb.LoadBalancerTargetGroupTable{ + ID: oldTg.ID, + Name: newTg.Name, + BkBizID: newTg.BkBizID, + Region: newTg.Region, + Protocol: newTg.Protocol, + Port: newTg.Port, + Weight: newTg.Weight, + Reviser: cts.Kit.User, + } + + if len(newTg.CloudVpcID) > 0 { + // 根据cloudVpcID查询VPC信息,如查不到vpcInfo则报错 + vpcInfo, ok := vpcInfoMap[newTg.CloudVpcID] + if !ok { + return nil, errf.Newf(errf.RecordNotFound, "vpcID[%s] not found", newTg.VpcID) + } + updateData.VpcID = vpcInfo.ID + updateData.CloudVpcID = vpcInfo.CloudID + } + if newTg.HealthCheck != nil { + mergedHealth, err := json.UpdateMerge(newTg.HealthCheck, string(oldTg.HealthCheck)) + if err != nil { + return nil, fmt.Errorf("json UpdateMerge target group health check failed, err: %v", err) + } + updateData.HealthCheck = tabletype.JsonField(mergedHealth) + } + updateDataList = append(updateDataList, updateData) + } + if err := svc.dao.LoadBalancerTargetGroup().UpdateBatch(cts.Kit, updateDataList); err != nil { + return nil, err + } + + return nil, nil +} + // BatchUpdateTCloudUrlRule .. func (svc *lbSvc) BatchUpdateTCloudUrlRule(cts *rest.Contexts) (any, error) { req := new(dataproto.TCloudUrlRuleBatchUpdateReq) diff --git a/cmd/hc-service/logics/res-sync/common/diff.go b/cmd/hc-service/logics/res-sync/common/diff.go index 89a376be1..88f330819 100644 --- a/cmd/hc-service/logics/res-sync/common/diff.go +++ b/cmd/hc-service/logics/res-sync/common/diff.go @@ -152,7 +152,9 @@ type CloudResType interface { typeslb.TCloudClb | typeslb.TCloudListener | typeslb.TCloudUrlRule | - typeslb.Backend + typeslb.Backend | + typeslb.TargetGroupBackend | + typeslb.TargetGroup } // DBResType 本地资源类型 @@ -248,7 +250,8 @@ type DBResType interface { corelb.TCloudLoadBalancer | corelb.TCloudLbUrlRule | corelb.TCloudListener | - corelb.BaseTarget + corelb.BaseTarget | + corelb.TargetGroup[corelb.TCloudTargetGroupExtension] } // Diff 对比云和db资源,划分出新增数据,更新数据,删除数据。 diff --git a/cmd/hc-service/logics/res-sync/tcloud/client.go b/cmd/hc-service/logics/res-sync/tcloud/client.go index 247cae29e..15ad581be 100644 --- a/cmd/hc-service/logics/res-sync/tcloud/client.go +++ b/cmd/hc-service/logics/res-sync/tcloud/client.go @@ -81,6 +81,9 @@ type Interface interface { LoadBalancerWithListener(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, error) Listener(kt *kit.Kit, opt *SyncListenerOfSingleLBOption) (*SyncResult, error) + + CloudTargetGroup(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, error) + RemoveTargetGroupDeleteFromCloud(kt *kit.Kit, accountID string, region string) error } var _ Interface = new(client) diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_cloud_target_group.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_cloud_target_group.go new file mode 100644 index 000000000..8b3e87426 --- /dev/null +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_cloud_target_group.go @@ -0,0 +1,458 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + "hcm/cmd/hc-service/logics/res-sync/common" + typescore "hcm/pkg/adaptor/types/core" + typeslb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/core" + corelb "hcm/pkg/api/core/cloud/load-balancer" + dataproto "hcm/pkg/api/data-service/cloud" + "hcm/pkg/criteria/constant" + "hcm/pkg/criteria/enumor" + "hcm/pkg/criteria/errf" + "hcm/pkg/criteria/validator" + "hcm/pkg/dal/dao/tools" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/tools/assert" + cvt "hcm/pkg/tools/converter" + "hcm/pkg/tools/slice" +) + +// CloudTargetGroup 同步指定负载均衡云目标组 +func (cli *client) CloudTargetGroup(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) (*SyncResult, error) { + if err := validator.ValidateTool(params, opt); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + cloudTgs, err := cli.listTargetGroupFromCloud(kt, params) + if err != nil { + logs.Errorf("fail to list target group from cloud for sync, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + + dbTgs, err := cli.listTargetGroupFromDB(kt, params) + if err != nil { + logs.Errorf("fail to list target group from database for sync, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + + if len(cloudTgs) == 0 && len(dbTgs) == 0 { + return new(SyncResult), nil + } + + // 比较基本信息 + addSlice, updateMap, delCloudIDs := common.Diff[typeslb.TargetGroup, corelb.TCloudTargetGroup](cloudTgs, dbTgs, + isTGChange) + + // 删除云上已经删除的负载均衡实例 + if err = cli.deleteTargetGroup(kt, params.AccountID, params.Region, delCloudIDs); err != nil { + return nil, err + } + + // 创建云上新增负载均衡实例 + _, err = cli.createTargetGroup(kt, params.AccountID, params.Region, addSlice) + if err != nil { + return nil, err + } + // 更新变更负载均衡 + if err = cli.updateTargetGroup(kt, updateMap); err != nil { + return nil, err + } + + // 同步目标组下的rs + + if err = cli.batchCloudTargetGroupRS(kt, params, opt); err != nil { + logs.Errorf("fail to sync cloud target group rs, err:%v, rid: %s", err, kt.Rid) + return nil, err + } + + return new(SyncResult), nil +} + +// RemoveTargetGroupDeleteFromCloud ... +func (cli *client) RemoveTargetGroupDeleteFromCloud(kt *kit.Kit, accountID string, region string) error { + req := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("region", region), + tools.RuleEqual("target_group_type", enumor.CloudTargetGroupType), + ), + Page: &core.BasePage{ + Start: 0, + Limit: constant.BatchOperationMaxLimit, + }, + } + for { + tgFromDB, err := cli.dbCli.Global.LoadBalancer.ListTargetGroup(kt, req) + if err != nil { + logs.Errorf("[%s] request dataservice to list target group failed, err: %v, req: %v, rid: %s", + enumor.TCloud, err, req, kt.Rid) + return err + } + + cloudIDs := slice.Map(tgFromDB.Details, func(tg corelb.BaseTargetGroup) string { return tg.CloudID }) + if len(cloudIDs) == 0 { + break + } + + var delCloudIDs []string + params := &SyncBaseParams{AccountID: accountID, Region: region, CloudIDs: cloudIDs} + delCloudIDs, err = cli.listRemovedTargetGroupID(kt, params) + if err != nil { + return err + } + + if len(delCloudIDs) != 0 { + if err = cli.deleteTargetGroup(kt, accountID, region, delCloudIDs); err != nil { + return err + } + } + + if len(tgFromDB.Details) < constant.BatchOperationMaxLimit { + break + } + + req.Page.Start += constant.BatchOperationMaxLimit + } + return nil +} + +func (cli *client) listRemovedTargetGroupID(kt *kit.Kit, params *SyncBaseParams) ([]string, error) { + if err := params.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + batchParam := &SyncBaseParams{ + AccountID: params.AccountID, + Region: params.Region, + } + tgMap := cvt.StringSliceToMap(params.CloudIDs) + + for _, batchCloudID := range slice.Split(params.CloudIDs, constant.TCLBDescribeMax) { + batchParam.CloudIDs = batchCloudID + found, err := cli.listTargetGroupFromCloud(kt, batchParam) + if err != nil { + return nil, err + } + for _, tg := range found { + delete(tgMap, tg.GetCloudID()) + } + } + + return cvt.MapKeyToSlice(tgMap), nil +} + +func (cli *client) listTargetGroupFromCloud(kt *kit.Kit, params *SyncBaseParams) ([]typeslb.TargetGroup, error) { + opt := &typeslb.ListTargetGroupOption{ + Region: params.Region, + TargetGroupIds: params.CloudIDs, + } + return cli.cloudCli.ListTargetGroup(kt, opt) + +} + +func (cli *client) listTargetGroupFromDB(kt *kit.Kit, params *SyncBaseParams) ([]corelb.TCloudTargetGroup, error) { + + listReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", params.AccountID), + tools.RuleEqual("target_group_type", enumor.CloudTargetGroupType), + tools.RuleEqual("region", params.Region)), + Page: core.NewDefaultBasePage(), + } + if len(params.CloudIDs) > 0 { + listReq.Filter.Rules = append(listReq.Filter.Rules, tools.RuleIn("cloud_id", params.CloudIDs)) + } + groupsResp, err := cli.dbCli.TCloud.LoadBalancer.ListTargetGroup(kt, listReq) + if err != nil { + logs.Errorf("fail to list target group for sync, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + return groupsResp.Details, nil +} + +// 删除目标组 +func (cli *client) deleteTargetGroup(kt *kit.Kit, accountID string, region string, cloudIDs []string) error { + + if len(cloudIDs) == 0 { + return nil + } + listReq := &core.ListReq{ + Filter: tools.ExpressionAnd( + tools.RuleEqual("account_id", accountID), + tools.RuleEqual("region", region), + tools.RuleIn("cloud_id", cloudIDs), + ), + Page: core.NewDefaultBasePage(), + } + return cli.dbCli.Global.LoadBalancer.DeleteTargetGroup(kt, listReq) +} + +// 创建目标组 +func (cli *client) createTargetGroup(kt *kit.Kit, accountID string, region string, + addSlice []typeslb.TargetGroup) ([]string, error) { + + if len(addSlice) == 0 { + return []string{}, nil + } + groups := make([]dataproto.TargetGroupBatchCreate[corelb.TCloudTargetGroupExtension], 0, len(addSlice)) + for _, cloud := range addSlice { + groups = append(groups, dataproto.TargetGroupBatchCreate[corelb.TCloudTargetGroupExtension]{ + Name: cvt.PtrToVal(cloud.TargetGroupName), + CloudID: cvt.PtrToVal(cloud.TargetGroupId), + Vendor: enumor.TCloud, + AccountID: accountID, + BkBizID: constant.UnassignedBiz, + Region: region, + // 云上目标组没有协议 + Protocol: enumor.HttpProtocol, + Port: int64(cvt.PtrToVal(cloud.Port)), + CloudVpcID: cvt.PtrToVal(cloud.VpcId), + TargetGroupType: enumor.CloudTargetGroupType, + }) + } + createReq := &dataproto.TCloudTargetGroupCreateReq{TargetGroups: groups} + createResult, err := cli.dbCli.TCloud.LoadBalancer.BatchCreateTCloudTargetGroup(kt, createReq) + if err != nil { + logs.Errorf("fail to create cloud target group, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + return createResult.IDs, nil +} + +// 更新目标组 +func (cli *client) updateTargetGroup(kt *kit.Kit, updateMap map[string]typeslb.TargetGroup) error { + + if len(updateMap) == 0 { + return nil + } + updates := make([]*dataproto.TargetGroupExtUpdateReq[corelb.TCloudTargetGroupExtension], 0, len(updateMap)) + for localID, cloudInfo := range updateMap { + updates = append(updates, &dataproto.TargetGroupExtUpdateReq[corelb.TCloudTargetGroupExtension]{ + ID: localID, + Name: cvt.PtrToVal(cloudInfo.TargetGroupName), + CloudVpcID: cvt.PtrToVal(cloudInfo.VpcId), + Port: int64(cvt.PtrToVal(cloudInfo.Port)), + }) + } + updateReq := &dataproto.TCloudTargetGroupBatchUpdateReq{TargetGroups: updates} + return cli.dbCli.TCloud.LoadBalancer.BatchUpdateTargetGroup(kt, updateReq) +} + +func (cli *client) batchCloudTargetGroupRS(kt *kit.Kit, params *SyncBaseParams, opt *SyncLBOption) error { + dbTgs, err := cli.listTargetGroupFromDB(kt, params) + if err != nil { + logs.Errorf("fail to list target group from database for sync rs, err: %v, rid: %s", err, kt.Rid) + return err + } + + for _, tg := range dbTgs { + cloudInstList, err := cli.listInstancesFromCloud(kt, params.Region, tg.CloudID) + if err != nil { + logs.Errorf("fail to list instance from cloud for sync ,err: %v, rid: %s", err, kt.Rid) + return err + } + dbTargetList, err := cli.listInstancesFromDB(kt, tg.ID) + if err != nil { + logs.Errorf("fail to list rs from db for sync, err: %v, rid: %s", err, kt.Rid) + return err + } + addSlice, updateMap, delCloudIDs := common.Diff[typeslb.TargetGroupBackend, corelb.BaseTarget](cloudInstList, + dbTargetList, isCloudRSChange) + + if err = cli.deleteCloudRs(kt, delCloudIDs); err != nil { + logs.Errorf("fail to delete cloud rs for sync, err: %v, rid: %s", err, kt.Rid) + return err + } + + if _, err := cli.createCloudRs(kt, params.AccountID, tg.ID, addSlice); err != nil { + logs.Errorf("fail to create cloud rs for sync, err: %v, rid: %s", err, kt.Rid) + return err + } + + if err := cli.updateCloudRs(kt, updateMap); err != nil { + logs.Errorf("fail to update cloud rs for sync, err: %v, rid: %s", err, kt.Rid) + return err + } + } + + return nil +} + +// 按cloudInstID 删除目标组中的rs +func (cli *client) deleteCloudRs(kt *kit.Kit, cloudIDs []string) error { + if len(cloudIDs) == 0 { + return nil + } + + delReq := &dataproto.LoadBalancerBatchDeleteReq{Filter: tools.ContainersExpression("cloud_inst_id", cloudIDs)} + err := cli.dbCli.Global.LoadBalancer.BatchDeleteTarget(kt, delReq) + if err != nil { + logs.Errorf("fail to delete cloud rs (ids=%v), err: %v, rid: %s", cloudIDs, err, kt.Rid) + return err + } + + return nil +} +func (cli *client) createCloudRs(kt *kit.Kit, accountID, tgId string, addSlice []typeslb.TargetGroupBackend) ( + []string, error) { + + if len(addSlice) == 0 { + return nil, nil + } + + var targets []*dataproto.TargetBaseReq + for _, backend := range addSlice { + dbVal := &dataproto.TargetBaseReq{ + InstType: cvt.PtrToVal((*enumor.InstType)(backend.Type)), + CloudInstID: cvt.PtrToVal(backend.InstanceId), + Port: int64(cvt.PtrToVal(backend.Port)), + AccountID: accountID, + TargetGroupID: tgId, + CloudTargetGroupID: cvt.PtrToVal(backend.TargetGroupId), + } + if backend.Weight != nil { + dbVal.Weight = cvt.ValToPtr((int64)(cvt.PtrToVal(backend.Weight))) + } + targets = append(targets, dbVal) + + } + + created, err := cli.dbCli.Global.LoadBalancer.BatchCreateTCloudTarget(kt, + &dataproto.TargetBatchCreateReq{Targets: targets}) + if err != nil { + logs.Errorf("fail to create target for target group syncing, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + return created.IDs, nil +} + +// 更新rs中的信息 +func (cli *client) updateCloudRs(kt *kit.Kit, updateMap map[string]typeslb.TargetGroupBackend) (err error) { + + if len(updateMap) == 0 { + return nil + } + updates := make([]*dataproto.TargetUpdate, 0, len(updateMap)) + for id, backend := range updateMap { + updates = append(updates, &dataproto.TargetUpdate{ + ID: id, + Port: int64(cvt.PtrToVal(backend.Port)), + Weight: cvt.ValToPtr((int64)(cvt.PtrToVal(backend.Weight))), + PrivateIPAddress: cvt.PtrToSlice(backend.PrivateIpAddresses), + PublicIPAddress: cvt.PtrToSlice(backend.PublicIpAddresses), + InstName: cvt.PtrToVal(backend.InstanceName), + }) + } + updateReq := &dataproto.TargetBatchUpdateReq{Targets: updates} + if err = cli.dbCli.Global.LoadBalancer.BatchUpdateTarget(kt, updateReq); err != nil { + logs.Errorf("fail to update targets while syncing, err: %v, rid:%s", err, kt.Rid) + } + + return err +} + +func (cli *client) listInstancesFromDB(kt *kit.Kit, tgID string) ([]corelb.BaseTarget, error) { + listReq := &core.ListReq{ + Filter: tools.EqualExpression("target_group_id", tgID), + Page: core.NewDefaultBasePage(), + } + var allTargets []corelb.BaseTarget + for { + targetResp, err := cli.dbCli.Global.LoadBalancer.ListTarget(kt, listReq) + if err != nil { + return nil, err + } + if len(targetResp.Details) == 0 { + break + } + allTargets = append(allTargets, targetResp.Details...) + listReq.Page.Start += uint32(len(targetResp.Details)) + } + return allTargets, nil +} + +func (cli *client) listInstancesFromCloud(kt *kit.Kit, region, tgCloudID string) ([]typeslb.TargetGroupBackend, error) { + req := &typeslb.ListTargetGroupInstanceOption{ + Region: region, + TargetGroupIds: []string{tgCloudID}, + Page: &typescore.TCloudPage{ + Offset: 0, + Limit: typescore.TCloudQueryLimit, + }, + } + allInstList := make([]typeslb.TargetGroupBackend, 0) + for { + instList, err := cli.cloudCli.ListTargetGroupInstance(kt, req) + if err != nil { + logs.Errorf("fail to list target group instance form cloud, err: %v, rid: %s", err, kt.Rid) + return nil, err + } + if len(instList) == 0 { + break + } + req.Page.Offset += uint64(len(instList)) + allInstList = append(allInstList, instList...) + } + return allInstList, nil +} + +func isTGChange(cloudTG typeslb.TargetGroup, localTG corelb.TCloudTargetGroup) bool { + + if cvt.PtrToVal(cloudTG.TargetGroupName) != localTG.Name { + return true + } + + if cvt.PtrToVal(cloudTG.VpcId) != localTG.CloudVpcID { + return true + } + + if cvt.PtrToVal(cloudTG.Port) != uint64(localTG.Port) { + return true + } + + return false +} + +// 判断rs信息是否变化 +func isCloudRSChange(cloud typeslb.TargetGroupBackend, db corelb.BaseTarget) bool { + if cvt.PtrToVal(cloud.Port) != uint64(db.Port) { + return true + } + + if cvt.PtrToVal(cloud.Weight) != uint64(cvt.PtrToVal(db.Weight)) { + return true + } + if cvt.PtrToVal(cloud.InstanceName) != db.InstName { + return true + } + + if !assert.IsStringSliceEqual(cvt.PtrToSlice(cloud.PrivateIpAddresses), db.PrivateIPAddress) { + return true + } + + if !assert.IsStringSliceEqual(cvt.PtrToSlice(cloud.PublicIpAddresses), db.PublicIPAddress) { + return true + } + return false +} diff --git a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target_group.go b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target.go similarity index 93% rename from cmd/hc-service/logics/res-sync/tcloud/load_balancer_target_group.go rename to cmd/hc-service/logics/res-sync/tcloud/load_balancer_target.go index a8de7485c..2c288b573 100644 --- a/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target_group.go +++ b/cmd/hc-service/logics/res-sync/tcloud/load_balancer_target.go @@ -57,12 +57,13 @@ func (cli *client) LocalTargetGroup(kt *kit.Kit, param *SyncBaseParams, opt *Syn cloudIDs = append(cloudIDs, listener.GetCloudID()) continue } + // 7层监听器,遍历下面的规则 for _, rule := range listener.Rules { healthMap[cvt.PtrToVal(rule.LocationId)] = rule.HealthCheck cloudIDs = append(cloudIDs, cvt.PtrToVal(rule.LocationId)) } } - tgCloudHealthMap, tgList, err := cli.getTargetGruop(kt, opt.LBID, cloudIDs, healthMap) + tgCloudHealthMap, tgList, err := cli.getTargetGroup(kt, opt.LBID, cloudIDs, healthMap) if err != nil { return err } @@ -72,12 +73,12 @@ func (cli *client) LocalTargetGroup(kt *kit.Kit, param *SyncBaseParams, opt *Syn continue } - // 更新 健康检查 + // 更新健康检查 updateReq := &dataproto.TargetGroupUpdateReq{ IDs: []string{tg.ID}, HealthCheck: convHealthCheck(tgCloudHealthMap[tg.CloudID]), } - err = cli.dbCli.TCloud.LoadBalancer.BatchUpdateTCloudTargetGroup(kt, updateReq) + err = cli.dbCli.TCloud.LoadBalancer.UpdateTargetGroup(kt, updateReq) if err != nil { logs.Errorf("fail to update target group health check during sync, err: %v, rid: %s", err, kt.Rid) return err @@ -87,7 +88,7 @@ func (cli *client) LocalTargetGroup(kt *kit.Kit, param *SyncBaseParams, opt *Syn return nil } -func (cli *client) getTargetGruop(kt *kit.Kit, lbId string, cloudIDs []string, +func (cli *client) getTargetGroup(kt *kit.Kit, lbId string, cloudIDs []string, healthMap map[string]*tclb.HealthCheck) (map[string]*tclb.HealthCheck, []corelb.BaseTargetGroup, error) { // 查找本地 目标组 @@ -144,21 +145,18 @@ func (cli *client) ListenerTargets(kt *kit.Kit, param *SyncBaseParams, opt *Sync if isTGHandled(tgId) { return nil } - // 存在则比较 return cli.compareTargetsChange(kt, opt.AccountID, tgId, cloudTargets, tgRsMap[tgId]) } - // 遍历云上的监听器、规则 + var layer4AddList = make([]typeslb.TCloudListenerTarget, 0) + var layer7AddList = make(map[string][]*tclb.RuleTargets) + var layer7ListenerMap = make(map[string]typeslb.TCloudListenerTarget) for _, listener := range cloudListenerTargets { if !listener.GetProtocol().IsLayer7Protocol() { - // ---- for layer 4 对比监听器变化 ---- + // layer 4 listener rel, exists := relMap[cvt.PtrToVal(listener.ListenerId)] if !exists { - // 云上监听器、但是没有对应目标组,则在同步时自动创建目标组,并将RS加入目标组。 - if err := cli.createLocalTargetGroupL4(kt, opt, lb, listener); err != nil { - logs.Errorf("fail to create local target group for layer 4 listener, rid: %s", kt.Rid) - return err - } + layer4AddList = append(layer4AddList, listener) // 只要本地没有目标组就跳过RS同步 continue } @@ -168,15 +166,14 @@ func (cli *client) ListenerTargets(kt *kit.Kit, param *SyncBaseParams, opt *Sync } continue } - // ---- for layer 7 对比规则变化 ---- + layer7ListenerMap[cvt.PtrToVal(listener.ListenerId)] = listener + // layer 7 规则 for _, rule := range listener.Rules { rel, exists := relMap[cvt.PtrToVal(rule.LocationId)] if !exists { - // 没有对应目标组关系,则在同步时自动创建目标组,并将RS加入目标组。 - if err := cli.createLocalTargetGroupL7(kt, opt, lb, listener, rule); err != nil { - logs.Errorf("fail to create local target group for layer 7 rule, rid: %s", kt.Rid) - return err - } + // 加入 7层待创建列表 + layer7AddList[cvt.PtrToVal(listener.ListenerId)] = append( + layer7AddList[cvt.PtrToVal(listener.ListenerId)], rule) // 跳过比较 continue } @@ -187,6 +184,23 @@ func (cli *client) ListenerTargets(kt *kit.Kit, param *SyncBaseParams, opt *Sync } } } + // 添加4层监听器 + // 云上监听器、但是没有对应目标组,则在同步时自动创建目标组,并将RS加入目标组。 + for _, listener := range layer4AddList { + if err := cli.createLocalTargetGroupL4(kt, opt, lb, listener); err != nil { + logs.Errorf("fail to create local target group for layer 4 listener, rid: %s", kt.Rid) + return err + } + } + // 添加7层规则 + for lblId, rules := range layer7AddList { + for _, rule := range rules { + if err := cli.createLocalTargetGroupL7(kt, opt, lb, layer7ListenerMap[lblId], rule); err != nil { + logs.Errorf("fail to create local target group for layer 7 rule, rid: %s", kt.Rid) + return err + } + } + } return nil } diff --git a/cmd/hc-service/service/sync/tcloud/load_balancer.go b/cmd/hc-service/service/sync/tcloud/load_balancer.go index e00022bb3..c551f6359 100644 --- a/cmd/hc-service/service/sync/tcloud/load_balancer.go +++ b/cmd/hc-service/service/sync/tcloud/load_balancer.go @@ -34,7 +34,7 @@ import ( ) // SyncLoadBalancer 同步负载均衡接口 -func (svc *service) SyncLoadBalancer(cts *rest.Contexts) (interface{}, error) { +func (svc *service) SyncLoadBalancer(cts *rest.Contexts) (any, error) { return nil, handler.ResourceSync(cts, &lbHandler{cli: svc.syncCli}) } diff --git a/cmd/hc-service/service/sync/tcloud/service.go b/cmd/hc-service/service/sync/tcloud/service.go index cd39a34a3..fe63434db 100644 --- a/cmd/hc-service/service/sync/tcloud/service.go +++ b/cmd/hc-service/service/sync/tcloud/service.go @@ -53,6 +53,7 @@ func InitService(cap *capability.Capability) { h.Add("SyncSubAccount", "POST", "/sub_accounts/sync", v.SyncSubAccount) h.Add("SyncArgsTpl", "POST", "/argument_templates/sync", v.SyncArgsTpl) h.Add("SyncCert", "POST", "/certs/sync", v.SyncCert) + h.Add("SyncTargetGroup", "POST", "/target_groups/sync", v.SyncTargetGroup) h.Add("SyncLoadBalancer", "POST", "/load_balancers/sync", v.SyncLoadBalancer) h.Load(cap.WebService) diff --git a/cmd/hc-service/service/sync/tcloud/target_group.go b/cmd/hc-service/service/sync/tcloud/target_group.go new file mode 100644 index 000000000..bc768db8d --- /dev/null +++ b/cmd/hc-service/service/sync/tcloud/target_group.go @@ -0,0 +1,123 @@ +/* + * TencentBlueKing is pleased to support the open source community by making + * 蓝鲸智云 - 混合云管理平台 (BlueKing - Hybrid Cloud Management System) available. + * Copyright (C) 2024 THL A29 Limited, + * a Tencent company. All rights reserved. + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at http://opensource.org/licenses/MIT + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * We undertake not to change the open source license (MIT license) applicable + * + * to the current version of the project delivered to anyone in the future. + */ + +package tcloud + +import ( + ressync "hcm/cmd/hc-service/logics/res-sync" + "hcm/cmd/hc-service/logics/res-sync/tcloud" + "hcm/cmd/hc-service/service/sync/handler" + typecore "hcm/pkg/adaptor/types/core" + typeclb "hcm/pkg/adaptor/types/load-balancer" + "hcm/pkg/api/hc-service/sync" + "hcm/pkg/criteria/enumor" + "hcm/pkg/kit" + "hcm/pkg/logs" + "hcm/pkg/rest" + "hcm/pkg/tools/converter" +) + +// SyncTargetGroup ... +func (svc *service) SyncTargetGroup(cts *rest.Contexts) (any, error) { + return nil, handler.ResourceSync(cts, &tgHandler{cli: svc.syncCli}) +} + +// tgHandler target group sync handler. +type tgHandler struct { + cli ressync.Interface + + request *sync.TCloudSyncReq + syncCli tcloud.Interface + offset uint64 +} + +var _ handler.Handler = new(tgHandler) + +// Prepare ... +func (hd *tgHandler) Prepare(cts *rest.Contexts) error { + request, syncCli, err := defaultPrepare(cts, hd.cli) + if err != nil { + return err + } + + hd.request = request + hd.syncCli = syncCli + + return nil +} + +// Next ... +func (hd *tgHandler) Next(kt *kit.Kit) ([]string, error) { + listOpt := &typeclb.ListTargetGroupOption{ + Region: hd.request.Region, + Page: &typecore.TCloudPage{ + Offset: hd.offset, + Limit: typecore.TCloudQueryLimit, + }, + } + + tgResult, err := hd.syncCli.CloudCli().ListTargetGroup(kt, listOpt) + if err != nil { + logs.Errorf("request adaptor list tcloud target group failed, err: %v, opt: %v, rid: %s", err, listOpt, kt.Rid) + return nil, err + } + + if len(tgResult) == 0 { + return nil, nil + } + + cloudIDs := make([]string, 0, len(tgResult)) + for _, one := range tgResult { + cloudIDs = append(cloudIDs, converter.PtrToVal(one.TargetGroupId)) + } + + hd.offset += typecore.TCloudQueryLimit + return cloudIDs, nil +} + +// Sync ... +func (hd *tgHandler) Sync(kt *kit.Kit, cloudIDs []string) error { + params := &tcloud.SyncBaseParams{ + AccountID: hd.request.AccountID, + Region: hd.request.Region, + CloudIDs: cloudIDs, + } + if _, err := hd.syncCli.CloudTargetGroup(kt, params, new(tcloud.SyncLBOption)); err != nil { + logs.Errorf("sync tcloud target group failed, err: %v, opt: %v, rid: %s", err, params, kt.Rid) + return err + } + + return nil +} + +// RemoveDeleteFromCloud ... +func (hd *tgHandler) RemoveDeleteFromCloud(kt *kit.Kit) error { + if err := hd.syncCli.RemoveTargetGroupDeleteFromCloud(kt, hd.request.AccountID, hd.request.Region); err != nil { + logs.Errorf("remove target group delete from cloud failed, err: %v, accountID: %s, region: %s, rid: %s", err, + hd.request.AccountID, hd.request.Region, kt.Rid) + return err + } + + return nil +} + +// Name target_group +func (hd *tgHandler) Name() enumor.CloudResourceType { + return enumor.TargetGroupCloudResType +} diff --git a/pkg/adaptor/tcloud/interface.go b/pkg/adaptor/tcloud/interface.go index 0f334fe30..ee01ce314 100644 --- a/pkg/adaptor/tcloud/interface.go +++ b/pkg/adaptor/tcloud/interface.go @@ -185,4 +185,7 @@ type TCloud interface { InquiryPriceLoadBalancer(kt *kit.Kit, opt *typelb.TCloudCreateClbOption) (*typelb.TCloudLBPrice, error) ListLoadBalancerQuota(kt *kit.Kit, opt *typelb.ListTCloudLoadBalancerQuotaOption) ( []typelb.TCloudLoadBalancerQuota, error) + ListTargetGroup(kt *kit.Kit, opt *typelb.ListTargetGroupOption) ([]typelb.TargetGroup, error) + ListTargetGroupInstance(kt *kit.Kit, opt *typelb.ListTargetGroupInstanceOption) ( + []typelb.TargetGroupBackend, error) } diff --git a/pkg/adaptor/tcloud/target.go b/pkg/adaptor/tcloud/target.go index d2bc4b15b..390c0d028 100644 --- a/pkg/adaptor/tcloud/target.go +++ b/pkg/adaptor/tcloud/target.go @@ -27,6 +27,7 @@ import ( "hcm/pkg/criteria/errf" "hcm/pkg/kit" "hcm/pkg/logs" + cvt "hcm/pkg/tools/converter" clb "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/clb/v20180317" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" @@ -113,3 +114,100 @@ func (t *TCloudImpl) ListTargetHealth(kt *kit.Kit, opt *typelb.TCloudListTargetH return healths, nil } + +// ListTargetGroup DescribeTargetGroupList 获取目标组列表 不返回关联关系 +// https://cloud.tencent.com/document/api/214/40555 +func (t *TCloudImpl) ListTargetGroup(kt *kit.Kit, opt *typelb.ListTargetGroupOption) ([]typelb.TargetGroup, error) { + if opt == nil { + return nil, errf.New(errf.InvalidParameter, "list option is required") + } + + if err := opt.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + client, err := t.clientSet.ClbClient(opt.Region) + if err != nil { + return nil, fmt.Errorf("new tcloud targets client failed, region: %s, err: %v", opt.Region, err) + } + + req := clb.NewDescribeTargetGroupListRequest() + if len(opt.TargetGroupIds) != 0 { + req.TargetGroupIds = common.StringPtrs(opt.TargetGroupIds) + } + + if opt.Page != nil { + req.Offset = cvt.ValToPtr(opt.Page.Offset) + req.Limit = cvt.ValToPtr(opt.Page.Limit) + } + + resp, err := client.DescribeTargetGroupListWithContext(kt.Ctx, req) + if err != nil { + logs.Errorf("list tcloud target group list failed, req: %+v, err: %v, rid: %s", req, err, kt.Rid) + return nil, err + } + + groups := make([]typelb.TargetGroup, 0, len(resp.Response.TargetGroupSet)) + for _, one := range resp.Response.TargetGroupSet { + groups = append(groups, typelb.TargetGroup{TargetGroupInfo: one}) + } + + return groups, nil +} + +// ListTargetGroupInstance DescribeTargetGroupInstances 获取目标组绑定的服务器 +// https://cloud.tencent.com/document/api/214/40556 +func (t *TCloudImpl) ListTargetGroupInstance(kt *kit.Kit, opt *typelb.ListTargetGroupInstanceOption) ( + []typelb.TargetGroupBackend, error) { + + if opt == nil { + return nil, errf.New(errf.InvalidParameter, "list option is required") + } + + if err := opt.Validate(); err != nil { + return nil, errf.NewFromErr(errf.InvalidParameter, err) + } + + client, err := t.clientSet.ClbClient(opt.Region) + if err != nil { + return nil, fmt.Errorf("new tcloud targets client failed, region: %s, err: %v", opt.Region, err) + } + + req := clb.NewDescribeTargetGroupInstancesRequest() + if len(opt.TargetGroupIds) != 0 { + req.Filters = append(req.Filters, &clb.Filter{ + Name: cvt.ValToPtr("TargetGroupId"), + Values: cvt.SliceToPtr(opt.TargetGroupIds), + }) + } + if len(opt.BindIPs) != 0 { + req.Filters = append(req.Filters, &clb.Filter{ + Name: cvt.ValToPtr("BindIP"), + Values: cvt.SliceToPtr(opt.BindIPs), + }) + } + if len(opt.InstanceIds) != 0 { + req.Filters = append(req.Filters, &clb.Filter{ + Name: cvt.ValToPtr("InstanceId"), + Values: cvt.SliceToPtr(opt.InstanceIds), + }) + } + + if opt.Page != nil { + req.Offset = cvt.ValToPtr(opt.Page.Offset) + req.Limit = cvt.ValToPtr(opt.Page.Limit) + } + + resp, err := client.DescribeTargetGroupInstancesWithContext(kt.Ctx, req) + if err != nil { + logs.Errorf("list tcloud target group list failed, req: %+v, err: %v, rid: %s", req, err, kt.Rid) + return nil, err + } + + groups := make([]typelb.TargetGroupBackend, 0, len(resp.Response.TargetGroupInstanceSet)) + for _, one := range resp.Response.TargetGroupInstanceSet { + groups = append(groups, typelb.TargetGroupBackend{TargetGroupBackend: one}) + } + + return groups, nil +} diff --git a/pkg/adaptor/types/load-balancer/tcloud.go b/pkg/adaptor/types/load-balancer/tcloud.go index fa78048c1..697d04c54 100644 --- a/pkg/adaptor/types/load-balancer/tcloud.go +++ b/pkg/adaptor/types/load-balancer/tcloud.go @@ -813,3 +813,49 @@ type TCloudLoadBalancerQuota struct { // 配额数量。 QuotaLimit int64 `json:"quota_limit,omitnil"` } + +// ListTargetGroupOption ... +type ListTargetGroupOption struct { + Region string `json:"region" validate:"required"` + TargetGroupIds []string `json:"target_group_ids" validate:"omitempty,dive,required"` + Page *core.TCloudPage `json:"page" validate:"omitempty"` +} + +// Validate ... +func (opt *ListTargetGroupOption) Validate() error { + return validator.Validate.Struct(opt) +} + +// TargetGroup ... +type TargetGroup struct { + *tclb.TargetGroupInfo +} + +// GetCloudID ... +func (g TargetGroup) GetCloudID() string { + return converter.PtrToVal(g.TargetGroupId) +} + +// ListTargetGroupInstanceOption ... +type ListTargetGroupInstanceOption struct { + Region string `json:"region" validate:"required"` + TargetGroupIds []string `json:"target_group_ids" validate:"omitempty,dive,required"` + BindIPs []string `json:"bind_ips" validate:"omitempty,dive,required"` + InstanceIds []string `json:"instance_ids" validate:"omitempty,dive,required"` + Page *core.TCloudPage `json:"page" validate:"omitempty"` +} + +// Validate ... +func (opt *ListTargetGroupInstanceOption) Validate() error { + return validator.Validate.Struct(opt) +} + +// TargetGroupBackend ... +type TargetGroupBackend struct { + *tclb.TargetGroupBackend +} + +// GetCloudID ... +func (rs TargetGroupBackend) GetCloudID() string { + return fmt.Sprintf("%s-%d", converter.PtrToVal(rs.InstanceId), converter.PtrToVal(rs.Port)) +} diff --git a/pkg/api/core/cloud/load-balancer/target_group.go b/pkg/api/core/cloud/load-balancer/target_group.go index 9ccd27eac..e09f0d7c5 100644 --- a/pkg/api/core/cloud/load-balancer/target_group.go +++ b/pkg/api/core/cloud/load-balancer/target_group.go @@ -68,6 +68,9 @@ type TargetGroupExtension interface { TCloudTargetGroupExtension } +// TCloudTargetGroup tcloud target group +type TCloudTargetGroup = TargetGroup[TCloudTargetGroupExtension] + // BaseTarget define base target. type BaseTarget struct { ID string `json:"id"` diff --git a/pkg/api/core/response.go b/pkg/api/core/response.go index a623bca8b..aef48151a 100644 --- a/pkg/api/core/response.go +++ b/pkg/api/core/response.go @@ -51,3 +51,10 @@ type BaseResp[T any] struct { rest.BaseResp `json:",inline"` Data T `json:"data"` } + +// ListResultWithTotal generic list result with total count +type ListResultWithTotal[T any] struct { + // total count not affected by page + TotalCount uint64 `json:"total_count"` + Details []T `json:"details"` +} diff --git a/pkg/api/data-service/cloud/target_group.go b/pkg/api/data-service/cloud/target_group.go index 25a7b5abd..a987f4c68 100644 --- a/pkg/api/data-service/cloud/target_group.go +++ b/pkg/api/data-service/cloud/target_group.go @@ -66,20 +66,21 @@ func (req *TargetGroupCreateReq) Validate() error { // TargetBaseReq Target基本参数 type TargetBaseReq struct { - ID string `json:"id" validate:"omitempty"` - InstType enumor.InstType `json:"inst_type" validate:"required"` - CloudInstID string `json:"cloud_inst_id" validate:"required"` - Port int64 `json:"port" validate:"required"` - Weight *int64 `json:"weight" validate:"required"` - AccountID string `json:"account_id,omitempty" validate:"omitempty"` - TargetGroupID string `json:"target_group_id,omitempty" validate:"omitempty"` - InstName string `json:"inst_name,omitempty" validate:"omitempty"` - PrivateIPAddress []string `json:"private_ip_address,omitempty" validate:"omitempty"` - PublicIPAddress []string `json:"public_ip_address,omitempty" validate:"omitempty"` - CloudVpcIDs []string `json:"cloud_vpc_ids,omitempty" validate:"omitempty"` - Zone string `json:"zone,omitempty" validate:"omitempty"` - NewPort *int64 `json:"new_port,omitempty" validate:"omitempty"` - NewWeight *int64 `json:"new_weight,omitempty" validate:"omitempty"` + ID string `json:"id" validate:"omitempty"` + InstType enumor.InstType `json:"inst_type" validate:"required"` + CloudInstID string `json:"cloud_inst_id" validate:"required"` + TargetGroupID string `json:"target_group_id,omitempty" validate:"omitempty"` + CloudTargetGroupID string `json:"cloud_target_group_id,omitempty" validate:"omitempty"` + Port int64 `json:"port" validate:"required"` + Weight *int64 `json:"weight" validate:"required"` + AccountID string `json:"account_id,omitempty" validate:"omitempty"` + InstName string `json:"inst_name,omitempty" validate:"omitempty"` + PrivateIPAddress []string `json:"private_ip_address,omitempty" validate:"omitempty"` + PublicIPAddress []string `json:"public_ip_address,omitempty" validate:"omitempty"` + CloudVpcIDs []string `json:"cloud_vpc_ids,omitempty" validate:"omitempty"` + Zone string `json:"zone,omitempty" validate:"omitempty"` + NewPort *int64 `json:"new_port,omitempty" validate:"omitempty"` + NewWeight *int64 `json:"new_weight,omitempty" validate:"omitempty"` } // Validate validate req(目前仅支持CVM的实例类型) @@ -109,13 +110,14 @@ type TCloudTargetGroupCreateReq = TargetGroupBatchCreateReq[corelb.TCloudTargetG // TargetGroupBatchCreate define target group batch create. type TargetGroupBatchCreate[Extension corelb.TargetGroupExtension] struct { + CloudID string `json:"cloud_id" validate:"omitempty"` Name string `json:"name" validate:"required"` Vendor enumor.Vendor `json:"vendor" validate:"required"` AccountID string `json:"account_id" validate:"required"` BkBizID int64 `json:"bk_biz_id" validate:"required"` Region string `json:"region" validate:"required"` Protocol enumor.ProtocolType `json:"protocol" validate:"required"` - Port int64 `json:"port" validate:"required"` + Port int64 `json:"port" validate:"omitempty"` VpcID string `json:"vpc_id" validate:"omitempty"` CloudVpcID string `json:"cloud_vpc_id" validate:"required"` TargetGroupType enumor.TargetGroupType `json:"target_group_type" validate:"omitempty"` @@ -200,24 +202,20 @@ func (req *TargetGroupUpdateReq) Validate() error { // TargetGroupExtUpdateReq ... type TargetGroupExtUpdateReq[T corelb.TargetGroupExtension] struct { - ID string `json:"id" validate:"required"` - Name string `json:"name"` - Vendor string `json:"vendor"` - AccountID string `json:"account_id"` - BkBizID int64 `json:"bk_biz_id"` - - TargetGroupType enumor.TargetGroupType `json:"target_group_type"` - VpcID string `json:"vpc_id"` - CloudVpcID string `json:"cloud_vpc_id"` - Region string `json:"region"` - Protocol enumor.ProtocolType `json:"protocol"` - Port int64 `json:"port"` - Weight int64 `json:"weight"` - HealthCheck types.JsonField `json:"health_check"` - - Memo *string `json:"memo"` - *core.Revision `json:",inline"` - Extension *T `json:"extension"` + ID string `json:"id" validate:"required"` + Name string `json:"name"` + BkBizID int64 `json:"bk_biz_id"` + + VpcID string `json:"vpc_id"` + CloudVpcID string `json:"cloud_vpc_id"` + Region string `json:"region"` + Protocol enumor.ProtocolType `json:"protocol"` + Port int64 `json:"port"` + Weight *int64 `json:"weight,omitempty"` + HealthCheck *corelb.TCloudHealthCheckInfo `json:"health_check"` + + Memo *string `json:"memo"` + Extension *T `json:"extension,omitempty"` } // Validate ... @@ -226,19 +224,18 @@ func (req *TargetGroupExtUpdateReq[T]) Validate() error { } // TargetGroupBatchUpdateReq 目标组批量更新参数 -type TargetGroupBatchUpdateReq[T corelb.TargetGroupExtension] []*TargetGroupExtUpdateReq[T] +type TargetGroupBatchUpdateReq[T corelb.TargetGroupExtension] struct { + TargetGroups []*TargetGroupExtUpdateReq[T] `json:"target_groups" validate:"required,min=1,dive,required"` +} // Validate ... func (req *TargetGroupBatchUpdateReq[T]) Validate() error { - for _, r := range *req { - if err := r.Validate(); err != nil { - return err - } - } - - return nil + return validator.Validate.Struct(req) } +// TCloudTargetGroupBatchUpdateReq ... +type TCloudTargetGroupBatchUpdateReq = TargetGroupBatchUpdateReq[corelb.TCloudTargetGroupExtension] + // -------------------------- List Target Group -------------------------- // TargetGroupListResult define target group list result. diff --git a/pkg/client/data-service/tcloud/load_balancer.go b/pkg/client/data-service/tcloud/load_balancer.go index b98e32113..8a1261be3 100644 --- a/pkg/client/data-service/tcloud/load_balancer.go +++ b/pkg/client/data-service/tcloud/load_balancer.go @@ -99,12 +99,20 @@ func (cli *LoadBalancerClient) BatchCreateTargetGroupWithRel(kt *kit.Kit, cli.client, rest.POST, kt, req, "/target_groups/with/rels/batch/create") } -// BatchUpdateTCloudTargetGroup 批量更新腾讯云目标组 -func (cli *LoadBalancerClient) BatchUpdateTCloudTargetGroup(kt *kit.Kit, req *dataproto.TargetGroupUpdateReq) error { +// UpdateTargetGroup 更新腾讯云目标组 +func (cli *LoadBalancerClient) UpdateTargetGroup(kt *kit.Kit, req *dataproto.TargetGroupUpdateReq) error { return common.RequestNoResp[dataproto.TargetGroupUpdateReq]( cli.client, rest.PATCH, kt, req, "/target_groups") } +// BatchUpdateTargetGroup ... +func (cli *LoadBalancerClient) BatchUpdateTargetGroup(kt *kit.Kit, + req *dataproto.TCloudTargetGroupBatchUpdateReq) error { + + return common.RequestNoResp[dataproto.TCloudTargetGroupBatchUpdateReq]( + cli.client, rest.PATCH, kt, req, "/target_groups/batch") +} + // GetTargetGroup 获取目标组详情 func (cli *LoadBalancerClient) GetTargetGroup(kt *kit.Kit, id string) ( *corelb.TargetGroup[corelb.TCloudTargetGroupExtension], error) { @@ -165,3 +173,11 @@ func (cli *LoadBalancerClient) ListListener(kt *kit.Kit, req *core.ListReq) ( return common.Request[core.ListReq, dataproto.TCloudListenerListResult](cli.client, rest.POST, kt, req, "/load_balancers/listeners/list") } + +// ListTargetGroup list target group. +func (cli *LoadBalancerClient) ListTargetGroup(kt *kit.Kit, req *core.ListReq) ( + *dataproto.TargetGroupExtListResult[corelb.TCloudTargetGroupExtension], error) { + + return common.Request[core.ListReq, dataproto.TargetGroupExtListResult[corelb.TCloudTargetGroupExtension]]( + cli.client, rest.POST, kt, req, "/load_balancers/target_groups/list") +} diff --git a/pkg/client/hc-service/tcloud/clb.go b/pkg/client/hc-service/tcloud/clb.go index dc4f7cb8d..3a89c8259 100644 --- a/pkg/client/hc-service/tcloud/clb.go +++ b/pkg/client/hc-service/tcloud/clb.go @@ -210,4 +210,10 @@ func (c *ClbClient) ListQuota(kt *kit.Kit, req *hcproto.TCloudListLoadBalancerQu return common.Request[hcproto.TCloudListLoadBalancerQuotaReq, []typelb.TCloudLoadBalancerQuota]( c.client, http.MethodPost, kt, req, "/load_balancers/quota") + +} + +// SyncTargetGroup ... +func (c *ClbClient) SyncTargetGroup(kt *kit.Kit, req *sync.TCloudSyncReq) error { + return common.RequestNoResp[sync.TCloudSyncReq](c.client, http.MethodPost, kt, req, "/target_groups/sync") }