fix: 完善多云同步验证与实例统计
- 修复华为云签名、账单字段与区域映射缓存 - 按真实云实例更新账号服务器数并补充并发测试
This commit is contained in:
1 parent
132fe9ba97
commit
9b895efee3
6 files changed
+226
-64
No files matched your search
@@ -33,24 +33,6 @@ func NewCloudAccountHandler(db *gorm.DB) *CloudAccountHandler {
|
||||
func (h *CloudAccountHandler) ListCloudAccounts(c *gin.Context) {
|
||||
var accounts []model.CloudAccount
|
||||
h.db.Find(&accounts)
|
||||
// 服务器数来自真实云账号关联,避免重复导入后被“本次新增数”覆盖。 // Logan.Li
|
||||
type accountServerCount struct {
|
||||
CloudAccountID uint
|
||||
Total int
|
||||
}
|
||||
var serverCounts []accountServerCount
|
||||
h.db.Model(&model.Server{}).
|
||||
Select("cloud_account_id, COUNT(*) AS total").
|
||||
Where("cloud_account_id IS NOT NULL").Group("cloud_account_id").Scan(&serverCounts)
|
||||
countByAccount := make(map[uint]int, len(serverCounts))
|
||||
for _, item := range serverCounts {
|
||||
countByAccount[item.CloudAccountID] = item.Total
|
||||
}
|
||||
for index := range accounts {
|
||||
if total, ok := countByAccount[accounts[index].ID]; ok {
|
||||
accounts[index].ServerCount = total
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"ezops/cloud"
|
||||
@@ -73,22 +74,60 @@ func (h *CloudSyncHandler) SyncRegions(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新账号的 region_count 和 last_sync_at
|
||||
h.db.Model(&account).Updates(map[string]interface{}{
|
||||
// 并发统计所有可用区域实例数,卡片展示云账号真实资源量。 // Logan.Li
|
||||
serverCount, countedRegions := countCloudInstances(provider, cloudRegions)
|
||||
updates := map[string]interface{}{
|
||||
"region_count": len(cloudRegions),
|
||||
"last_sync_at": time.Now(),
|
||||
})
|
||||
}
|
||||
if countedRegions > 0 {
|
||||
updates["server_count"] = serverCount
|
||||
}
|
||||
// 更新账号区域、实例数量和同步时间。 // Logan.Li
|
||||
h.db.Model(&account).Updates(updates)
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Message: "同步成功",
|
||||
Data: gin.H{
|
||||
"synced": synced,
|
||||
"total": len(cloudRegions),
|
||||
"synced": synced,
|
||||
"total": len(cloudRegions),
|
||||
"server_count": serverCount,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// countCloudInstances 以固定并发统计各区域实例数量,避免串行请求过慢。 // Logan.Li
|
||||
func countCloudInstances(provider cloud.Provider, regions []cloud.CloudRegion) (total int, succeeded int) {
|
||||
type countResult struct {
|
||||
count int
|
||||
ok bool
|
||||
}
|
||||
results := make(chan countResult, len(regions))
|
||||
semaphore := make(chan struct{}, 4)
|
||||
var waitGroup sync.WaitGroup
|
||||
for _, region := range regions {
|
||||
regionID := region.RegionID
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
instances, err := provider.ListInstances(regionID)
|
||||
results <- countResult{count: len(instances), ok: err == nil}
|
||||
}()
|
||||
}
|
||||
waitGroup.Wait()
|
||||
close(results)
|
||||
for result := range results {
|
||||
if result.ok {
|
||||
total += result.count
|
||||
succeeded++
|
||||
}
|
||||
}
|
||||
return total, succeeded
|
||||
}
|
||||
|
||||
// GetAccountRegions 获取指定云账号的区域列表(按 provider 过滤)
|
||||
// GET /cloud-accounts/:id/regions
|
||||
func (h *CloudSyncHandler) GetAccountRegions(c *gin.Context) {
|
||||
@@ -308,11 +347,6 @@ func (h *CloudSyncHandler) ImportInstances(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存该账号当前已关联的资产总数,而不是本次新增数。 // Logan.Li
|
||||
var serverCount int64
|
||||
h.db.Model(&model.Server{}).Where("cloud_account_id = ?", account.ID).Count(&serverCount)
|
||||
h.db.Model(&account).UpdateColumn("server_count", serverCount)
|
||||
|
||||
c.JSON(http.StatusOK, model.Response{
|
||||
Code: 200,
|
||||
Message: "导入完成",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"ezops/cloud"
|
||||
)
|
||||
|
||||
type countInstancesProvider struct {
|
||||
counts map[string]int
|
||||
failures map[string]bool
|
||||
activeCalls atomic.Int32
|
||||
maxCalls atomic.Int32
|
||||
}
|
||||
|
||||
func (p *countInstancesProvider) Name() string { return "fake" }
|
||||
|
||||
func (p *countInstancesProvider) ListRegions() ([]cloud.CloudRegion, error) { return nil, nil }
|
||||
|
||||
func (p *countInstancesProvider) ListInstances(regionID string) ([]cloud.CloudInstance, error) {
|
||||
active := p.activeCalls.Add(1)
|
||||
defer p.activeCalls.Add(-1)
|
||||
for {
|
||||
maximum := p.maxCalls.Load()
|
||||
if active <= maximum || p.maxCalls.CompareAndSwap(maximum, active) {
|
||||
break
|
||||
}
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
if p.failures[regionID] {
|
||||
return nil, errors.New("区域请求失败")
|
||||
}
|
||||
return make([]cloud.CloudInstance, p.counts[regionID]), nil
|
||||
}
|
||||
|
||||
func TestCountCloudInstances(t *testing.T) {
|
||||
provider := &countInstancesProvider{
|
||||
counts: map[string]int{"a": 2, "b": 3, "c": 4, "d": 5, "e": 6},
|
||||
failures: map[string]bool{"c": true},
|
||||
}
|
||||
regions := []cloud.CloudRegion{{RegionID: "a"}, {RegionID: "b"}, {RegionID: "c"}, {RegionID: "d"}, {RegionID: "e"}}
|
||||
total, succeeded := countCloudInstances(provider, regions)
|
||||
if total != 16 || succeeded != 4 {
|
||||
t.Fatalf("实例统计错误: total=%d succeeded=%d", total, succeeded)
|
||||
}
|
||||
if maximum := provider.maxCalls.Load(); maximum > 4 {
|
||||
t.Fatalf("并发数超过限制: %d", maximum)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user