mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-07 18:26:37 +08:00
fix(backend): handle federation service arrays in runtime command (#206)
## Summary - accept both top-level service arrays and wrapped `services` payloads in federation runtime command parsing - fix forward runtime service binding and port-range validation for remote shared-node AddService/UpdateService calls - add regression tests for top-level array payload handling to prevent listener cleanup/flow mapping regressions ## Verification - go test ./internal/http/handler -run "TestBindPeerShareForwardRuntimeServicesAcceptsTopLevelServiceArray|TestValidateFederationCommandPortsAcceptsTopLevelServiceArray|TestBindPeerShareForwardRuntimeServicesOnlyBindsForwardRole|TestCleanOrphanedServicesSkipsActiveSharedForwardRuntimeServices|TestProcessFlowItemTracksPeerShareFlowByForwardServiceName" - go test ./... - make build
This commit is contained in:
@@ -1277,26 +1277,31 @@ type federationForwardServiceBinding struct {
|
|||||||
Port int
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFederationForwardServiceBindings(data interface{}) []federationForwardServiceBinding {
|
func extractFederationServiceEntries(data interface{}) []map[string]interface{} {
|
||||||
|
if data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if entries := asMapSlice(data); len(entries) > 0 {
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
dataMap, ok := data.(map[string]interface{})
|
dataMap, ok := data.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rawServices, ok := dataMap["services"]
|
|
||||||
if !ok {
|
if entries := asMapSlice(dataMap["services"]); len(entries) > 0 {
|
||||||
return nil
|
return entries
|
||||||
}
|
|
||||||
serviceList, ok := rawServices.([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFederationForwardServiceBindings(data interface{}) []federationForwardServiceBinding {
|
||||||
|
serviceList := extractFederationServiceEntries(data)
|
||||||
bindings := make([]federationForwardServiceBinding, 0, len(serviceList))
|
bindings := make([]federationForwardServiceBinding, 0, len(serviceList))
|
||||||
for _, item := range serviceList {
|
for _, svcMap := range serviceList {
|
||||||
svcMap, ok := item.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := normalizeForwardRuntimeServiceName(asString(svcMap["name"]))
|
name := normalizeForwardRuntimeServiceName(asString(svcMap["name"]))
|
||||||
if name == "" {
|
if name == "" {
|
||||||
continue
|
continue
|
||||||
@@ -1365,36 +1370,26 @@ func validateFederationCommandPorts(share *repo.PeerShare, data interface{}) err
|
|||||||
if share == nil || (share.PortRangeStart <= 0 && share.PortRangeEnd <= 0) {
|
if share == nil || (share.PortRangeStart <= 0 && share.PortRangeEnd <= 0) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dataMap, ok := data.(map[string]interface{})
|
|
||||||
if !ok {
|
serviceList := extractFederationServiceEntries(data)
|
||||||
|
if len(serviceList) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
for _, svcMap := range serviceList {
|
||||||
if services, ok := dataMap["services"]; ok {
|
addr := asString(svcMap["addr"])
|
||||||
serviceList, ok := services.([]interface{})
|
if addr == "" {
|
||||||
if !ok {
|
continue
|
||||||
return fmt.Errorf("invalid services format")
|
|
||||||
}
|
}
|
||||||
for _, svc := range serviceList {
|
_, portStr, err := net.SplitHostPort(addr)
|
||||||
svcMap, ok := svc.(map[string]interface{})
|
if err != nil {
|
||||||
if !ok {
|
return fmt.Errorf("invalid service address: %s", addr)
|
||||||
return fmt.Errorf("invalid service entry format")
|
}
|
||||||
}
|
port, err := strconv.Atoi(portStr)
|
||||||
addr, ok := svcMap["addr"].(string)
|
if err != nil || port <= 0 {
|
||||||
if !ok || addr == "" {
|
return fmt.Errorf("invalid port in service address: %s", addr)
|
||||||
continue
|
}
|
||||||
}
|
if port < share.PortRangeStart || port > share.PortRangeEnd {
|
||||||
_, portStr, err := net.SplitHostPort(addr)
|
return fmt.Errorf("port %d out of allowed range %d-%d", port, share.PortRangeStart, share.PortRangeEnd)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid service address: %s", addr)
|
|
||||||
}
|
|
||||||
port, err := strconv.Atoi(portStr)
|
|
||||||
if err != nil || port <= 0 {
|
|
||||||
return fmt.Errorf("invalid port in service address: %s", addr)
|
|
||||||
}
|
|
||||||
if port < share.PortRangeStart || port > share.PortRangeEnd {
|
|
||||||
return fmt.Errorf("port %d out of allowed range %d-%d", port, share.PortRangeStart, share.PortRangeEnd)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -670,6 +670,72 @@ func TestBindPeerShareForwardRuntimeServicesOnlyBindsForwardRole(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBindPeerShareForwardRuntimeServicesAcceptsTopLevelServiceArray(t *testing.T) {
|
||||||
|
r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open sqlite: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = r.Close() })
|
||||||
|
|
||||||
|
h := New(r, "test-jwt-secret")
|
||||||
|
now := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
if err := r.CreatePeerShare(&repo.PeerShare{
|
||||||
|
Name: "bind-array-share",
|
||||||
|
NodeID: 1,
|
||||||
|
Token: "bind-array-token",
|
||||||
|
MaxBandwidth: 0,
|
||||||
|
CurrentFlow: 0,
|
||||||
|
PortRangeStart: 26100,
|
||||||
|
PortRangeEnd: 26120,
|
||||||
|
IsActive: 1,
|
||||||
|
CreatedTime: now,
|
||||||
|
UpdatedTime: now,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("create share: %v", err)
|
||||||
|
}
|
||||||
|
share, err := r.GetPeerShareByToken("bind-array-token")
|
||||||
|
if err != nil || share == nil {
|
||||||
|
t.Fatalf("load share: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.DB().Exec(`
|
||||||
|
INSERT INTO peer_share_runtime(id, share_id, node_id, reservation_id, resource_key, binding_id, role, chain_name, service_name, protocol, strategy, port, target, applied, status, created_time, updated_time)
|
||||||
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
`,
|
||||||
|
1, share.ID, share.NodeID, "bind-array-r1", "bind-array-rk1", "", "forward", "", "", "tcp", "fifo", 26101, "", 0, 1, now, now,
|
||||||
|
).Error; err != nil {
|
||||||
|
t.Fatalf("insert runtime: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.bindPeerShareForwardRuntimeServices(share, []interface{}{
|
||||||
|
map[string]interface{}{"name": "99_2_10_tcp", "addr": "[::]:26101"},
|
||||||
|
map[string]interface{}{"name": "99_2_10_udp", "addr": "[::]:26101"},
|
||||||
|
})
|
||||||
|
|
||||||
|
forwardServiceName := ""
|
||||||
|
if err := r.DB().Raw(`SELECT service_name FROM peer_share_runtime WHERE id = 1`).Scan(&forwardServiceName).Error; err != nil {
|
||||||
|
t.Fatalf("load forward runtime service name: %v", err)
|
||||||
|
}
|
||||||
|
if forwardServiceName != "99_2_10" {
|
||||||
|
t.Fatalf("expected forward runtime service name bound from top-level array, got %q", forwardServiceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateFederationCommandPortsAcceptsTopLevelServiceArray(t *testing.T) {
|
||||||
|
share := &repo.PeerShare{
|
||||||
|
PortRangeStart: 26200,
|
||||||
|
PortRangeEnd: 26210,
|
||||||
|
}
|
||||||
|
err := validateFederationCommandPorts(share, []interface{}{
|
||||||
|
map[string]interface{}{"name": "11_2_10_tcp", "addr": "[::]:26201"},
|
||||||
|
map[string]interface{}{"name": "11_2_10_udp", "addr": "[::]:26201"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected top-level service array to pass port validation, got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFederationRemoteUsageList(t *testing.T) {
|
func TestFederationRemoteUsageList(t *testing.T) {
|
||||||
r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db"))
|
r, err := repo.Open(filepath.Join(t.TempDir(), "panel.db"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user