将 Worker 发现、路由画像、容量与执行传输抽象为平台无关接口,新增 Kubernetes 和静态容量适配器,并以 shadow 模式接入生产配置。 实现网络与容量评分、路由防抖、池队列、同步 Worker 租约、一次性执行令牌,以及提交状态不明时禁止重复分配的安全语义。 新增 0105 兼容迁移、管理接口、指标、OpenAPI 和回归测试。已执行全量 Go 测试、go vet、OpenAPI、迁移安全、Compose 与 Kustomize 验证。
144 lines
6.1 KiB
Go
144 lines
6.1 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/executionpool"
|
|
)
|
|
|
|
type AdminExecutionPoolWorker struct {
|
|
WorkerID string `json:"workerId"`
|
|
InstanceID string `json:"instanceId"`
|
|
Revision string `json:"revision,omitempty"`
|
|
ProtocolVersion string `json:"protocolVersion"`
|
|
Capabilities map[string]any `json:"capabilities"`
|
|
Allocated int `json:"allocated"`
|
|
SafeCapacity int `json:"safeCapacity"`
|
|
ActiveTasks int `json:"activeTasks"`
|
|
PressureState string `json:"pressureState"`
|
|
HeartbeatAt time.Time `json:"heartbeatAt"`
|
|
}
|
|
|
|
type AdminExecutionPool struct {
|
|
PoolID string `json:"poolId"`
|
|
Labels map[string]string `json:"labels"`
|
|
Capabilities map[string]any `json:"capabilities"`
|
|
State string `json:"state"`
|
|
Capacity *AdminExecutionPoolCapacity `json:"capacity,omitempty"`
|
|
Workers []AdminExecutionPoolWorker `json:"workers"`
|
|
}
|
|
|
|
type AdminExecutionPoolCapacity struct {
|
|
WorkerCount int `json:"workerCount"`
|
|
Allocated int `json:"allocated"`
|
|
SafeCapacity int `json:"safeCapacity"`
|
|
ActiveTasks int `json:"activeTasks"`
|
|
HeavyCapacity int `json:"heavyCapacity"`
|
|
HeavyTasks int `json:"heavyTasks"`
|
|
QueueDepth int `json:"queueDepth"`
|
|
EstimatedQueueWaitMilli int64 `json:"estimatedQueueWaitMs"`
|
|
ResourceHeadroom float64 `json:"resourceHeadroom"`
|
|
Stability float64 `json:"stability"`
|
|
Critical bool `json:"critical"`
|
|
SampledAt time.Time `json:"sampledAt"`
|
|
}
|
|
|
|
type AdminRouteHealth struct {
|
|
PoolID string `json:"poolId"`
|
|
RouteProfileKey string `json:"routeProfileKey"`
|
|
State string `json:"state"`
|
|
SuccessRate float64 `json:"successRate"`
|
|
ConnectTLSP95Milli int64 `json:"connectTlsP95Ms"`
|
|
FirstByteP95Milli int64 `json:"firstByteP95Ms"`
|
|
UploadBytesPerSecond float64 `json:"uploadBytesPerSecond"`
|
|
JitterP95Milli int64 `json:"jitterP95Ms"`
|
|
ConsecutiveFailures int `json:"consecutiveFailures"`
|
|
ConsecutiveSuccesses int `json:"consecutiveSuccesses"`
|
|
SampleCount int `json:"sampleCount"`
|
|
SampledAt time.Time `json:"sampledAt"`
|
|
ExpiresAt time.Time `json:"expiresAt"`
|
|
}
|
|
|
|
type AdminExecutionPoolResponse struct {
|
|
Pools []AdminExecutionPool `json:"pools"`
|
|
RouteHealth []AdminRouteHealth `json:"routeHealth"`
|
|
CapturedAt time.Time `json:"capturedAt"`
|
|
}
|
|
|
|
// listExecutionPools godoc
|
|
// @Summary 查询逻辑执行池、Worker 容量和上游路由健康状态
|
|
// @Tags Admin Runtime
|
|
// @Produce json
|
|
// @Success 200 {object} AdminExecutionPoolResponse
|
|
// @Failure 500 {object} ErrorEnvelope
|
|
// @Security BearerAuth
|
|
// @Router /api/admin/runtime/execution-pools [get]
|
|
func (s *Server) listExecutionPools(w http.ResponseWriter, r *http.Request) {
|
|
now := time.Now()
|
|
pools, err := s.coordinationStore.ListExecutionPools(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
workers, err := s.coordinationStore.ListWorkers(r.Context(), now)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
capacities, err := s.coordinationStore.ListCapacity(r.Context(), now)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
health, err := s.coordinationStore.ListAllRouteHealth(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
capacityByPool := make(map[string]executionpool.CapacitySnapshot, len(capacities))
|
|
for _, capacity := range capacities {
|
|
capacityByPool[capacity.PoolID] = capacity
|
|
}
|
|
workersByPool := make(map[string][]AdminExecutionPoolWorker)
|
|
for _, worker := range workers {
|
|
workersByPool[worker.PoolID] = append(workersByPool[worker.PoolID], AdminExecutionPoolWorker{
|
|
WorkerID: worker.WorkerID, InstanceID: worker.InstanceID, Revision: worker.Revision,
|
|
ProtocolVersion: worker.ProtocolVersion, Capabilities: worker.Capabilities,
|
|
Allocated: worker.Allocated, SafeCapacity: worker.SafeCapacity,
|
|
ActiveTasks: worker.ActiveTasks, PressureState: worker.PressureState,
|
|
HeartbeatAt: worker.HeartbeatAt,
|
|
})
|
|
}
|
|
response := AdminExecutionPoolResponse{
|
|
Pools: make([]AdminExecutionPool, 0, len(pools)), RouteHealth: make([]AdminRouteHealth, 0, len(health)), CapturedAt: now,
|
|
}
|
|
for _, item := range health {
|
|
response.RouteHealth = append(response.RouteHealth, AdminRouteHealth{
|
|
PoolID: item.PoolID, RouteProfileKey: item.RouteProfileKey, State: string(item.State),
|
|
SuccessRate: item.SuccessRate, ConnectTLSP95Milli: item.ConnectTLSP95.Milliseconds(),
|
|
FirstByteP95Milli: item.FirstByteP95.Milliseconds(), UploadBytesPerSecond: item.UploadBytesPerSecond,
|
|
JitterP95Milli: item.JitterP95.Milliseconds(), ConsecutiveFailures: item.ConsecutiveFailures,
|
|
ConsecutiveSuccesses: item.ConsecutiveSuccesses, SampleCount: item.SampleCount,
|
|
SampledAt: item.SampledAt, ExpiresAt: item.ExpiresAt,
|
|
})
|
|
}
|
|
for _, pool := range pools {
|
|
item := AdminExecutionPool{
|
|
PoolID: pool.ID, Labels: pool.Labels, Capabilities: pool.Capabilities,
|
|
State: string(pool.State), Workers: workersByPool[pool.ID],
|
|
}
|
|
if capacity, ok := capacityByPool[pool.ID]; ok {
|
|
item.Capacity = &AdminExecutionPoolCapacity{
|
|
WorkerCount: capacity.WorkerCount, Allocated: capacity.Allocated, SafeCapacity: capacity.SafeCapacity,
|
|
ActiveTasks: capacity.ActiveTasks, HeavyCapacity: capacity.HeavyCapacity, HeavyTasks: capacity.HeavyTasks,
|
|
QueueDepth: capacity.QueueDepth, EstimatedQueueWaitMilli: capacity.EstimatedQueueWait.Milliseconds(),
|
|
ResourceHeadroom: capacity.ResourceHeadroom, Stability: capacity.Stability,
|
|
Critical: capacity.Critical, SampledAt: capacity.SampledAt,
|
|
}
|
|
}
|
|
response.Pools = append(response.Pools, item)
|
|
}
|
|
writeJSON(w, http.StatusOK, response)
|
|
}
|