2025-11-03 17:35:03 +08:00
|
|
|
// Package handler provides HTTP handlers for various endpoints.
|
|
|
|
|
package handler
|
|
|
|
|
|
|
|
|
|
import (
|
2025-11-04 17:12:15 +08:00
|
|
|
"context"
|
2025-11-03 17:35:03 +08:00
|
|
|
"fmt"
|
2025-11-11 17:37:06 +08:00
|
|
|
"maps"
|
2025-11-03 17:35:03 +08:00
|
|
|
"net/http"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"modelRT/constants"
|
2025-11-05 18:20:54 +08:00
|
|
|
"modelRT/database"
|
2025-11-03 17:35:03 +08:00
|
|
|
"modelRT/logger"
|
|
|
|
|
"modelRT/network"
|
2025-11-10 17:32:18 +08:00
|
|
|
"modelRT/orm"
|
2025-11-27 16:59:03 +08:00
|
|
|
"modelRT/util"
|
2025-11-03 17:35:03 +08:00
|
|
|
|
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
|
"github.com/gofrs/uuid"
|
2025-11-05 18:20:54 +08:00
|
|
|
"gorm.io/gorm"
|
2025-11-03 17:35:03 +08:00
|
|
|
)
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
var globalSubState *SharedSubState
|
2025-11-03 17:35:03 +08:00
|
|
|
|
|
|
|
|
func init() {
|
2025-11-13 17:29:49 +08:00
|
|
|
globalSubState = NewSharedSubState()
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-08 17:11:07 +08:00
|
|
|
// RealTimeSubHandler define real time data subscriptions process API
|
|
|
|
|
// @Summary 开始或结束订阅实时数据
|
2025-11-13 11:48:26 +08:00
|
|
|
// @Description 根据用户输入的组件token,从 modelRT 服务中开始或结束对于量测节点的实时数据的订阅
|
2025-11-03 17:35:03 +08:00
|
|
|
// @Tags RealTime Component
|
|
|
|
|
// @Accept json
|
|
|
|
|
// @Produce json
|
2025-11-13 11:48:26 +08:00
|
|
|
// @Param request body network.RealTimeSubRequest true "量测节点实时数据订阅"
|
|
|
|
|
// @Success 200 {object} network.SuccessResponse{payload=network.RealTimeSubPayload} "订阅实时数据结果列表"
|
2025-11-06 17:22:14 +08:00
|
|
|
//
|
|
|
|
|
// @Example 200 {
|
|
|
|
|
// "code": 200,
|
|
|
|
|
// "msg": "success",
|
|
|
|
|
// "payload": {
|
|
|
|
|
// "targets": [
|
|
|
|
|
// {
|
2025-12-02 17:26:15 +08:00
|
|
|
// "id": "grid1.zone1.station1.ns1.tag1.bay.I11_C_rms",
|
2025-11-06 17:22:14 +08:00
|
|
|
// "code": "1001",
|
|
|
|
|
// "msg": "subscription success"
|
|
|
|
|
// },
|
|
|
|
|
// {
|
2025-12-02 17:26:15 +08:00
|
|
|
// "id": "grid1.zone1.station1.ns1.tag1.bay.I11_B_rms",
|
2025-11-06 17:22:14 +08:00
|
|
|
// "code": "1002",
|
|
|
|
|
// "msg": "subscription failed"
|
|
|
|
|
// }
|
|
|
|
|
// ]
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
2025-11-13 11:48:26 +08:00
|
|
|
// @Failure 400 {object} network.FailureResponse{payload=network.RealTimeSubPayload} "订阅实时数据结果列表"
|
2025-11-06 17:22:14 +08:00
|
|
|
//
|
|
|
|
|
// @Example 400 {
|
|
|
|
|
// "code": 400,
|
|
|
|
|
// "msg": "failed to get recommend data from redis",
|
|
|
|
|
// "payload": {
|
|
|
|
|
// "targets": [
|
|
|
|
|
// {
|
2025-12-02 17:26:15 +08:00
|
|
|
// "id": "grid1.zone1.station1.ns1.tag1.bay.I11_A_rms",
|
2025-11-06 17:22:14 +08:00
|
|
|
// "code": "1002",
|
|
|
|
|
// "msg": "subscription failed"
|
|
|
|
|
// },
|
|
|
|
|
// {
|
2025-12-02 17:26:15 +08:00
|
|
|
// "id": "grid1.zone1.station1.ns1.tag1.bay.I11_B_rms",
|
2025-11-06 17:22:14 +08:00
|
|
|
// "code": "1002",
|
|
|
|
|
// "msg": "subscription failed"
|
|
|
|
|
// }
|
|
|
|
|
// ]
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
2025-11-08 17:11:07 +08:00
|
|
|
// @Router /monitors/data/subscriptions [post]
|
|
|
|
|
func RealTimeSubHandler(c *gin.Context) {
|
2025-11-06 17:22:14 +08:00
|
|
|
var request network.RealTimeSubRequest
|
2025-11-13 11:48:26 +08:00
|
|
|
var subAction string
|
2025-11-11 17:37:06 +08:00
|
|
|
var clientID string
|
2025-11-03 17:35:03 +08:00
|
|
|
|
|
|
|
|
if err := c.ShouldBindJSON(&request); err != nil {
|
|
|
|
|
logger.Error(c, "failed to unmarshal real time query request", "error", err)
|
|
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 11:48:26 +08:00
|
|
|
if request.Action == constants.SubStartAction && request.ClientID == "" {
|
|
|
|
|
subAction = request.Action
|
2025-11-03 17:35:03 +08:00
|
|
|
id, err := uuid.NewV4()
|
|
|
|
|
if err != nil {
|
2025-11-13 11:48:26 +08:00
|
|
|
logger.Error(c, "failed to generate client id", "error", err)
|
2025-11-03 17:35:03 +08:00
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
clientID = id.String()
|
2025-11-13 11:48:26 +08:00
|
|
|
} else if request.Action == constants.SubStartAction && request.ClientID != "" {
|
|
|
|
|
subAction = constants.SubAppendAction
|
2025-11-11 17:37:06 +08:00
|
|
|
clientID = request.ClientID
|
2025-11-13 11:48:26 +08:00
|
|
|
} else if request.Action == constants.SubStopAction && request.ClientID != "" {
|
|
|
|
|
subAction = request.Action
|
2025-11-11 17:37:06 +08:00
|
|
|
clientID = request.ClientID
|
2025-12-02 17:26:15 +08:00
|
|
|
} else if request.Action == constants.SubUpdateAction && request.ClientID != "" {
|
|
|
|
|
subAction = request.Action
|
|
|
|
|
clientID = request.ClientID
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-05 18:20:54 +08:00
|
|
|
pgClient := database.GetPostgresDBClient()
|
|
|
|
|
// open transaction
|
|
|
|
|
tx := pgClient.Begin()
|
|
|
|
|
defer tx.Commit()
|
|
|
|
|
|
2025-11-13 11:48:26 +08:00
|
|
|
switch subAction {
|
|
|
|
|
case constants.SubStartAction:
|
2025-11-26 17:49:24 +08:00
|
|
|
results, err := globalSubState.CreateConfig(c, tx, clientID, request.Measurements)
|
2025-11-04 17:12:15 +08:00
|
|
|
if err != nil {
|
2025-11-13 11:48:26 +08:00
|
|
|
logger.Error(c, "create real time data subscription config failed", "error", err)
|
2025-11-04 17:12:15 +08:00
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-06 17:22:14 +08:00
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
2025-11-04 17:12:15 +08:00
|
|
|
})
|
|
|
|
|
return
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
2025-11-04 17:12:15 +08:00
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, network.SuccessResponse{
|
|
|
|
|
Code: http.StatusOK,
|
|
|
|
|
Msg: "success",
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-05 18:20:54 +08:00
|
|
|
TargetResults: results,
|
2025-11-04 17:12:15 +08:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return
|
2025-11-13 11:48:26 +08:00
|
|
|
case constants.SubStopAction:
|
2025-11-26 17:49:24 +08:00
|
|
|
results, err := globalSubState.RemoveTargets(c, clientID, request.Measurements)
|
2025-11-06 17:22:14 +08:00
|
|
|
if err != nil {
|
2025-11-13 11:48:26 +08:00
|
|
|
logger.Error(c, "remove target to real time data subscription config failed", "error", err)
|
2025-11-06 17:22:14 +08:00
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-06 17:22:14 +08:00
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, network.SuccessResponse{
|
|
|
|
|
Code: http.StatusOK,
|
|
|
|
|
Msg: "success",
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-06 17:22:14 +08:00
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return
|
2025-11-13 11:48:26 +08:00
|
|
|
case constants.SubAppendAction:
|
2025-11-26 17:49:24 +08:00
|
|
|
results, err := globalSubState.AppendTargets(c, tx, clientID, request.Measurements)
|
2025-11-04 17:12:15 +08:00
|
|
|
if err != nil {
|
2025-11-13 11:48:26 +08:00
|
|
|
logger.Error(c, "append target to real time data subscription config failed", "error", err)
|
2025-11-04 17:12:15 +08:00
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-06 17:22:14 +08:00
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
2025-11-04 17:12:15 +08:00
|
|
|
})
|
|
|
|
|
return
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
2025-11-06 17:22:14 +08:00
|
|
|
|
2025-12-02 17:26:15 +08:00
|
|
|
c.JSON(http.StatusOK, network.SuccessResponse{
|
|
|
|
|
Code: http.StatusOK,
|
|
|
|
|
Msg: "success",
|
|
|
|
|
Payload: network.RealTimeSubPayload{
|
|
|
|
|
ClientID: clientID,
|
|
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
case constants.SubUpdateAction:
|
|
|
|
|
results, err := globalSubState.UpdateTargets(c, tx, clientID, request.Measurements)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error(c, "update target to real time data subscription config failed", "error", err)
|
|
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
|
|
|
|
Payload: network.RealTimeSubPayload{
|
|
|
|
|
ClientID: clientID,
|
|
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 17:22:14 +08:00
|
|
|
c.JSON(http.StatusOK, network.SuccessResponse{
|
|
|
|
|
Code: http.StatusOK,
|
|
|
|
|
Msg: "success",
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-06 17:22:14 +08:00
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
return
|
2025-11-03 17:35:03 +08:00
|
|
|
default:
|
2025-11-06 17:22:14 +08:00
|
|
|
err := fmt.Errorf("%w: request action is %s", constants.ErrUnsupportedAction, request.Action)
|
2025-11-13 11:48:26 +08:00
|
|
|
logger.Error(c, "unsupported action of real time data subscription request", "error", err)
|
2025-11-26 17:49:24 +08:00
|
|
|
requestTargetsCount := processRealTimeRequestCount(request.Measurements)
|
|
|
|
|
results := processRealTimeRequestTargets(request.Measurements, requestTargetsCount, err)
|
2025-11-03 17:35:03 +08:00
|
|
|
c.JSON(http.StatusOK, network.FailureResponse{
|
|
|
|
|
Code: http.StatusBadRequest,
|
|
|
|
|
Msg: err.Error(),
|
2025-11-12 17:34:18 +08:00
|
|
|
Payload: network.RealTimeSubPayload{
|
2025-11-11 17:37:06 +08:00
|
|
|
ClientID: clientID,
|
2025-11-06 17:22:14 +08:00
|
|
|
TargetResults: results,
|
|
|
|
|
},
|
2025-11-03 17:35:03 +08:00
|
|
|
})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-26 17:49:24 +08:00
|
|
|
// RealTimeSubMeasurement define struct of real time subscription measurement
|
|
|
|
|
type RealTimeSubMeasurement struct {
|
|
|
|
|
targets []string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TargetPollingContext define struct of real time pulling reverse context
|
|
|
|
|
type TargetPollingContext struct {
|
|
|
|
|
interval string
|
|
|
|
|
measurement *orm.Measurement
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// RealTimeSubConfig define struct of real time subscription config
|
|
|
|
|
type RealTimeSubConfig struct {
|
2025-11-26 17:49:24 +08:00
|
|
|
noticeChan chan *transportTargets
|
|
|
|
|
mutex sync.RWMutex
|
|
|
|
|
measurements map[string][]string
|
|
|
|
|
targetContext map[string]*TargetPollingContext
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// SharedSubState define struct of shared subscription state with mutex
|
|
|
|
|
type SharedSubState struct {
|
|
|
|
|
subMap map[string]*RealTimeSubConfig
|
2025-11-11 17:37:06 +08:00
|
|
|
globalMutex sync.RWMutex
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// NewSharedSubState define function to create new SharedSubState
|
|
|
|
|
func NewSharedSubState() *SharedSubState {
|
|
|
|
|
return &SharedSubState{
|
|
|
|
|
subMap: make(map[string]*RealTimeSubConfig),
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:26:15 +08:00
|
|
|
// processAndValidateTargetsForStart define func to perform all database I/O operations in a lock-free state for start action
|
|
|
|
|
func processAndValidateTargetsForStart(ctx context.Context, tx *gorm.DB, measurements []network.RealTimeMeasurementItem, allReqTargetNum int) (
|
2025-11-26 17:49:24 +08:00
|
|
|
[]network.TargetResult, []string,
|
|
|
|
|
map[string][]string,
|
|
|
|
|
map[string]*TargetPollingContext,
|
2025-11-11 17:37:06 +08:00
|
|
|
) {
|
|
|
|
|
targetProcessResults := make([]network.TargetResult, 0, allReqTargetNum)
|
2025-11-26 17:49:24 +08:00
|
|
|
newMeasMap := make(map[string][]string)
|
2025-11-11 17:37:06 +08:00
|
|
|
successfulTargets := make([]string, 0, allReqTargetNum)
|
2025-11-26 17:49:24 +08:00
|
|
|
newMeasContextMap := make(map[string]*TargetPollingContext)
|
2025-11-04 17:12:15 +08:00
|
|
|
|
2025-11-26 17:49:24 +08:00
|
|
|
for _, measurementItem := range measurements {
|
|
|
|
|
interval := measurementItem.Interval
|
|
|
|
|
for _, target := range measurementItem.Targets {
|
2025-11-05 18:20:54 +08:00
|
|
|
var targetResult network.TargetResult
|
|
|
|
|
targetResult.ID = target
|
2025-11-06 17:22:14 +08:00
|
|
|
targetModel, err := database.ParseDataIdentifierToken(ctx, tx, target)
|
2025-11-05 18:20:54 +08:00
|
|
|
if err != nil {
|
|
|
|
|
logger.Error(ctx, "parse data indentity token failed", "error", err, "identity_token", target)
|
|
|
|
|
targetResult.Code = constants.SubFailedCode
|
|
|
|
|
targetResult.Msg = fmt.Sprintf("%s: %s", constants.SubFailedMsg, err.Error())
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
targetResult.Code = constants.SubSuccessCode
|
|
|
|
|
targetResult.Msg = constants.SubSuccessMsg
|
2025-11-06 17:22:14 +08:00
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
2025-11-11 17:37:06 +08:00
|
|
|
successfulTargets = append(successfulTargets, target)
|
|
|
|
|
|
2025-11-26 17:49:24 +08:00
|
|
|
if _, ok := newMeasMap[interval]; !ok {
|
|
|
|
|
newMeasMap[interval] = make([]string, 0, len(measurementItem.Targets))
|
2025-11-05 18:20:54 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
|
2025-11-26 17:49:24 +08:00
|
|
|
meas := newMeasMap[interval]
|
|
|
|
|
meas = append(meas, target)
|
2025-11-27 16:59:03 +08:00
|
|
|
newMeasMap[interval] = meas
|
2025-11-26 17:49:24 +08:00
|
|
|
newMeasContextMap[target] = &TargetPollingContext{
|
|
|
|
|
interval: interval,
|
|
|
|
|
measurement: targetModel.GetMeasurementInfo(),
|
|
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-26 17:49:24 +08:00
|
|
|
return targetProcessResults, successfulTargets, newMeasMap, newMeasContextMap
|
2025-11-11 17:37:06 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:26:15 +08:00
|
|
|
// processAndValidateTargetsForUpdate define func to perform all database I/O operations in a lock-free state for update action
|
|
|
|
|
func processAndValidateTargetsForUpdate(ctx context.Context, tx *gorm.DB, config *RealTimeSubConfig, measurements []network.RealTimeMeasurementItem, allReqTargetNum int) (
|
|
|
|
|
[]network.TargetResult, []string,
|
|
|
|
|
map[string][]string,
|
|
|
|
|
map[string]*TargetPollingContext,
|
|
|
|
|
) {
|
|
|
|
|
targetProcessResults := make([]network.TargetResult, 0, allReqTargetNum)
|
|
|
|
|
newMeasMap := make(map[string][]string)
|
|
|
|
|
successfulTargets := make([]string, 0, allReqTargetNum)
|
|
|
|
|
newMeasContextMap := make(map[string]*TargetPollingContext)
|
|
|
|
|
|
|
|
|
|
for _, measurementItem := range measurements {
|
|
|
|
|
interval := measurementItem.Interval
|
|
|
|
|
for _, target := range measurementItem.Targets {
|
|
|
|
|
targetResult := network.TargetResult{ID: target}
|
|
|
|
|
if _, exist := config.targetContext[target]; !exist {
|
|
|
|
|
err := fmt.Errorf("target %s does not exists in subscription list", target)
|
|
|
|
|
logger.Error(ctx, "update target does not exist in subscription list", "error", err, "target", target)
|
|
|
|
|
targetResult.Code = constants.UpdateSubFailedCode
|
|
|
|
|
targetResult.Msg = fmt.Sprintf("%s: %s", constants.UpdateSubFailedMsg, err.Error())
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetModel, err := database.ParseDataIdentifierToken(ctx, tx, target)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error(ctx, "parse data indentity token failed", "error", err, "identity_token", target)
|
|
|
|
|
targetResult.Code = constants.UpdateSubFailedCode
|
|
|
|
|
targetResult.Msg = fmt.Sprintf("%s: %s", constants.UpdateSubFailedMsg, err.Error())
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetResult.Code = constants.UpdateSubSuccessCode
|
|
|
|
|
targetResult.Msg = constants.UpdateSubSuccessMsg
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
successfulTargets = append(successfulTargets, target)
|
|
|
|
|
|
|
|
|
|
if _, ok := newMeasMap[interval]; !ok {
|
|
|
|
|
newMeasMap[interval] = make([]string, 0, len(measurementItem.Targets))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
meas := newMeasMap[interval]
|
|
|
|
|
meas = append(meas, target)
|
|
|
|
|
newMeasMap[interval] = meas
|
|
|
|
|
newMeasContextMap[target] = &TargetPollingContext{
|
|
|
|
|
interval: interval,
|
|
|
|
|
measurement: targetModel.GetMeasurementInfo(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return targetProcessResults, successfulTargets, newMeasMap, newMeasContextMap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// mergeMeasurementsForStart define func to merge newMeasurementsMap into existingMeasurementsMap for start action
|
|
|
|
|
func mergeMeasurementsForStart(config *RealTimeSubConfig, newMeasurements map[string][]string, newMeasurementsContextMap map[string]*TargetPollingContext) []string {
|
2025-11-27 16:59:03 +08:00
|
|
|
allDuplicates := make([]string, 0)
|
2025-11-26 17:49:24 +08:00
|
|
|
for interval, newMeas := range newMeasurements {
|
|
|
|
|
if existingMeas, ok := config.measurements[interval]; ok {
|
2025-11-27 16:59:03 +08:00
|
|
|
// deduplication operations prevent duplicate subscriptions to the same measurement node
|
|
|
|
|
deduplicated, duplicates := util.DeduplicateAndReportDuplicates(existingMeas, newMeas)
|
|
|
|
|
|
|
|
|
|
if len(duplicates) > 0 {
|
|
|
|
|
for _, duplicate := range duplicates {
|
|
|
|
|
delete(newMeasurementsContextMap, duplicate)
|
|
|
|
|
}
|
|
|
|
|
allDuplicates = append(allDuplicates, duplicates...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(deduplicated) > 0 {
|
|
|
|
|
existingMeas = append(existingMeas, deduplicated...)
|
|
|
|
|
config.measurements[interval] = existingMeas
|
|
|
|
|
maps.Copy(config.targetContext, newMeasurementsContextMap)
|
|
|
|
|
}
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-27 16:59:03 +08:00
|
|
|
return allDuplicates
|
2025-11-11 17:37:06 +08:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 17:26:15 +08:00
|
|
|
// mergeMeasurementsForUpdate define func to merge newMeasurementsMap into existingMeasurementsMap for update action
|
|
|
|
|
func mergeMeasurementsForUpdate(config *RealTimeSubConfig, newMeasurements map[string][]string, newMeasurementsContextMap map[string]*TargetPollingContext) ([]string, error) {
|
|
|
|
|
allDuplicates := make([]string, 0)
|
|
|
|
|
delMeasMap := make(map[string][]string)
|
|
|
|
|
for _, newMeas := range newMeasurements {
|
|
|
|
|
for _, measurement := range newMeas {
|
|
|
|
|
oldInterval := config.targetContext[measurement].interval
|
|
|
|
|
if _, ok := delMeasMap[oldInterval]; !ok {
|
|
|
|
|
delMeasurements := []string{measurement}
|
|
|
|
|
delMeasMap[oldInterval] = delMeasurements
|
|
|
|
|
} else {
|
|
|
|
|
delMeasurements := delMeasMap[oldInterval]
|
|
|
|
|
delMeasurements = append(delMeasurements, measurement)
|
|
|
|
|
delMeasMap[oldInterval] = delMeasurements
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for interval, delMeas := range delMeasMap {
|
|
|
|
|
existingMeas, exist := config.measurements[interval]
|
|
|
|
|
if !exist {
|
|
|
|
|
return nil, fmt.Errorf("can not find exist measurements in %s interval", interval)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
measurements := util.RemoveTargetsFromSliceSimple(existingMeas, delMeas)
|
|
|
|
|
config.measurements[interval] = measurements
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for interval, newMeas := range newMeasurements {
|
|
|
|
|
if existingMeas, ok := config.measurements[interval]; ok {
|
|
|
|
|
deduplicated, duplicates := util.DeduplicateAndReportDuplicates(existingMeas, newMeas)
|
|
|
|
|
|
|
|
|
|
if len(duplicates) > 0 {
|
|
|
|
|
for _, duplicate := range duplicates {
|
|
|
|
|
delete(newMeasurementsContextMap, duplicate)
|
|
|
|
|
}
|
|
|
|
|
allDuplicates = append(allDuplicates, duplicates...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(deduplicated) > 0 {
|
|
|
|
|
existingMeas = append(existingMeas, deduplicated...)
|
|
|
|
|
config.measurements[interval] = existingMeas
|
|
|
|
|
maps.Copy(config.targetContext, newMeasurementsContextMap)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return allDuplicates, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// CreateConfig define function to create config in SharedSubState
|
2025-11-27 16:59:03 +08:00
|
|
|
func (s *SharedSubState) CreateConfig(ctx context.Context, tx *gorm.DB, clientID string, measurements []network.RealTimeMeasurementItem) ([]network.TargetResult, error) {
|
|
|
|
|
requestTargetsCount := processRealTimeRequestCount(measurements)
|
2025-12-02 17:26:15 +08:00
|
|
|
targetProcessResults, _, newMeasurementsMap, measurementContexts := processAndValidateTargetsForStart(ctx, tx, measurements, requestTargetsCount)
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.Lock()
|
2025-11-13 11:48:26 +08:00
|
|
|
if _, exist := s.subMap[clientID]; exist {
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.Unlock()
|
|
|
|
|
err := fmt.Errorf("clientID %s already exists. use AppendTargets to modify existing config", clientID)
|
|
|
|
|
logger.Error(ctx, "clientID already exists. use AppendTargets to modify existing config", "error", err)
|
|
|
|
|
return targetProcessResults, err
|
|
|
|
|
}
|
2025-11-04 17:12:15 +08:00
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
config := &RealTimeSubConfig{
|
2025-11-26 17:49:24 +08:00
|
|
|
noticeChan: make(chan *transportTargets, constants.NoticeChanCap),
|
|
|
|
|
measurements: newMeasurementsMap,
|
2025-11-27 16:59:03 +08:00
|
|
|
targetContext: measurementContexts,
|
2025-11-11 17:37:06 +08:00
|
|
|
}
|
2025-11-13 11:48:26 +08:00
|
|
|
s.subMap[clientID] = config
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.Unlock()
|
2025-11-05 18:20:54 +08:00
|
|
|
return targetProcessResults, nil
|
2025-11-03 17:35:03 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// AppendTargets define function to append targets in SharedSubState
|
2025-11-27 16:59:03 +08:00
|
|
|
func (s *SharedSubState) AppendTargets(ctx context.Context, tx *gorm.DB, clientID string, measurements []network.RealTimeMeasurementItem) ([]network.TargetResult, error) {
|
|
|
|
|
requestTargetsCount := processRealTimeRequestCount(measurements)
|
2025-11-06 17:22:14 +08:00
|
|
|
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.RLock()
|
2025-11-13 11:48:26 +08:00
|
|
|
config, exist := s.subMap[clientID]
|
2025-11-26 17:49:24 +08:00
|
|
|
s.globalMutex.RUnlock()
|
|
|
|
|
|
2025-11-04 17:12:15 +08:00
|
|
|
if !exist {
|
2025-11-11 17:37:06 +08:00
|
|
|
err := fmt.Errorf("clientID %s not found. use CreateConfig to start a new config", clientID)
|
|
|
|
|
logger.Error(ctx, "clientID not found. use CreateConfig to start a new config", "error", err)
|
2025-11-27 16:59:03 +08:00
|
|
|
return processRealTimeRequestTargets(measurements, requestTargetsCount, err), err
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
|
2025-12-02 17:26:15 +08:00
|
|
|
targetProcessResults, successfulTargets, newMeasMap, newMeasContextMap := processAndValidateTargetsForStart(ctx, tx, measurements, requestTargetsCount)
|
2025-11-11 17:37:06 +08:00
|
|
|
|
|
|
|
|
config.mutex.Lock()
|
2025-12-02 17:26:15 +08:00
|
|
|
allDuplicates := mergeMeasurementsForStart(config, newMeasMap, newMeasContextMap)
|
2025-11-27 16:59:03 +08:00
|
|
|
if len(allDuplicates) > 0 {
|
|
|
|
|
logger.Warn(ctx, "some targets are duplicate and have been ignored in append operation", "clientID", clientID, "duplicates", allDuplicates)
|
|
|
|
|
// process repeat target in targetProcessResults and successfulTargets
|
|
|
|
|
targetProcessResults, successfulTargets = filterAndDeduplicateRepeatTargets(targetProcessResults, successfulTargets, allDuplicates)
|
|
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
config.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
if len(successfulTargets) > 0 {
|
|
|
|
|
transportTargets := &transportTargets{
|
|
|
|
|
OperationType: constants.OpAppend,
|
|
|
|
|
Targets: successfulTargets,
|
|
|
|
|
}
|
|
|
|
|
config.noticeChan <- transportTargets
|
|
|
|
|
}
|
2025-11-04 17:12:15 +08:00
|
|
|
|
2025-11-06 17:22:14 +08:00
|
|
|
return targetProcessResults, nil
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-27 16:59:03 +08:00
|
|
|
func filterAndDeduplicateRepeatTargets(resultsSlice []network.TargetResult, idsSlice []string, duplicates []string) ([]network.TargetResult, []string) {
|
|
|
|
|
filteredIDs := make([]string, 0, len(idsSlice))
|
|
|
|
|
set := make(map[string]struct{}, len(duplicates))
|
|
|
|
|
for _, duplicate := range duplicates {
|
|
|
|
|
set[duplicate] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for index := range resultsSlice {
|
|
|
|
|
if _, isTarget := set[resultsSlice[index].ID]; isTarget {
|
|
|
|
|
resultsSlice[index].Code = constants.SubRepeatCode
|
|
|
|
|
resultsSlice[index].Msg = constants.SubRepeatMsg
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, id := range idsSlice {
|
|
|
|
|
if _, isTarget := set[id]; !isTarget {
|
|
|
|
|
filteredIDs = append(filteredIDs, id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return resultsSlice, filteredIDs
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// UpsertTargets define function to upsert targets in SharedSubState
|
2025-11-26 17:49:24 +08:00
|
|
|
func (s *SharedSubState) UpsertTargets(ctx context.Context, tx *gorm.DB, clientID string, measurements []network.RealTimeMeasurementItem) ([]network.TargetResult, error) {
|
|
|
|
|
requestTargetsCount := processRealTimeRequestCount(measurements)
|
2025-12-02 17:26:15 +08:00
|
|
|
targetProcessResults, successfulTargets, newMeasMap, newMeasContextMap := processAndValidateTargetsForStart(ctx, tx, measurements, requestTargetsCount)
|
2025-11-11 17:37:06 +08:00
|
|
|
|
|
|
|
|
s.globalMutex.RLock()
|
2025-11-13 11:48:26 +08:00
|
|
|
config, exist := s.subMap[clientID]
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
var opType constants.TargetOperationType
|
|
|
|
|
if exist {
|
|
|
|
|
opType = constants.OpUpdate
|
|
|
|
|
config.mutex.Lock()
|
2025-12-02 17:26:15 +08:00
|
|
|
mergeMeasurementsForStart(config, newMeasMap, newMeasContextMap)
|
2025-11-11 17:37:06 +08:00
|
|
|
config.mutex.Unlock()
|
|
|
|
|
} else {
|
|
|
|
|
opType = constants.OpAppend
|
|
|
|
|
s.globalMutex.Lock()
|
2025-11-13 11:48:26 +08:00
|
|
|
if config, exist = s.subMap[clientID]; !exist {
|
2025-11-13 17:29:49 +08:00
|
|
|
config = &RealTimeSubConfig{
|
2025-11-26 17:49:24 +08:00
|
|
|
noticeChan: make(chan *transportTargets, constants.NoticeChanCap),
|
|
|
|
|
measurements: newMeasMap,
|
2025-11-06 17:22:14 +08:00
|
|
|
}
|
2025-11-13 11:48:26 +08:00
|
|
|
s.subMap[clientID] = config
|
2025-11-11 17:37:06 +08:00
|
|
|
} else {
|
|
|
|
|
s.globalMutex.Unlock()
|
|
|
|
|
config.mutex.Lock()
|
2025-12-02 17:26:15 +08:00
|
|
|
mergeMeasurementsForStart(config, newMeasMap, newMeasContextMap)
|
2025-11-11 17:37:06 +08:00
|
|
|
config.mutex.Unlock()
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.Unlock()
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-11 17:37:06 +08:00
|
|
|
if len(successfulTargets) > 0 {
|
|
|
|
|
transportTargets := &transportTargets{
|
|
|
|
|
OperationType: opType,
|
|
|
|
|
Targets: successfulTargets,
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
config.noticeChan <- transportTargets
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-06 17:22:14 +08:00
|
|
|
return targetProcessResults, nil
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// RemoveTargets define function to remove targets in SharedSubState
|
2025-11-26 17:49:24 +08:00
|
|
|
func (s *SharedSubState) RemoveTargets(ctx context.Context, clientID string, measurements []network.RealTimeMeasurementItem) ([]network.TargetResult, error) {
|
|
|
|
|
requestTargetsCount := processRealTimeRequestCount(measurements)
|
2025-11-06 17:22:14 +08:00
|
|
|
targetProcessResults := make([]network.TargetResult, 0, requestTargetsCount)
|
|
|
|
|
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.RLock()
|
2025-11-13 11:48:26 +08:00
|
|
|
config, exist := s.subMap[clientID]
|
2025-11-04 17:12:15 +08:00
|
|
|
if !exist {
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.RUnlock()
|
|
|
|
|
err := fmt.Errorf("clientID %s not found", clientID)
|
|
|
|
|
logger.Error(ctx, "clientID not found in remove targets operation", "error", err)
|
2025-11-26 17:49:24 +08:00
|
|
|
return processRealTimeRequestTargets(measurements, requestTargetsCount, err), err
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.RUnlock()
|
2025-11-04 17:12:15 +08:00
|
|
|
|
2025-11-11 17:37:06 +08:00
|
|
|
var shouldRemoveClient bool
|
2025-11-26 17:49:24 +08:00
|
|
|
// measurements is the list of items to be removed passed in the request
|
2025-11-10 17:32:18 +08:00
|
|
|
transportTargets := &transportTargets{
|
|
|
|
|
OperationType: constants.OpRemove,
|
2025-11-11 17:37:06 +08:00
|
|
|
Targets: make([]string, 0, requestTargetsCount),
|
2025-11-10 17:32:18 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
config.mutex.Lock()
|
2025-11-26 17:49:24 +08:00
|
|
|
for _, measurement := range measurements {
|
|
|
|
|
interval := measurement.Interval
|
|
|
|
|
// meas is the locally running listener configuration
|
|
|
|
|
measTargets, measExist := config.measurements[interval]
|
|
|
|
|
if !measExist {
|
|
|
|
|
logger.Error(ctx, fmt.Sprintf("measurement with interval %s not found under clientID %s", interval, clientID), "clientID", clientID, "interval", interval)
|
|
|
|
|
for _, target := range measTargets {
|
2025-11-06 17:22:14 +08:00
|
|
|
targetResult := network.TargetResult{
|
|
|
|
|
ID: target,
|
|
|
|
|
Code: constants.CancelSubFailedCode,
|
|
|
|
|
Msg: constants.CancelSubFailedMsg,
|
|
|
|
|
}
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
}
|
2025-11-04 17:12:15 +08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetsToRemoveMap := make(map[string]struct{})
|
2025-11-26 17:49:24 +08:00
|
|
|
for _, target := range measurement.Targets {
|
2025-11-04 17:12:15 +08:00
|
|
|
targetsToRemoveMap[target] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var newTargets []string
|
2025-11-26 17:49:24 +08:00
|
|
|
for _, existingTarget := range measTargets {
|
2025-11-04 17:12:15 +08:00
|
|
|
if _, found := targetsToRemoveMap[existingTarget]; !found {
|
|
|
|
|
newTargets = append(newTargets, existingTarget)
|
2025-11-06 17:22:14 +08:00
|
|
|
} else {
|
2025-11-10 17:32:18 +08:00
|
|
|
transportTargets.Targets = append(transportTargets.Targets, existingTarget)
|
2025-11-06 17:22:14 +08:00
|
|
|
targetResult := network.TargetResult{
|
|
|
|
|
ID: existingTarget,
|
|
|
|
|
Code: constants.CancelSubSuccessCode,
|
|
|
|
|
Msg: constants.CancelSubSuccessMsg,
|
|
|
|
|
}
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
delete(targetsToRemoveMap, existingTarget)
|
2025-11-26 17:49:24 +08:00
|
|
|
delete(config.targetContext, existingTarget)
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-11-26 17:49:24 +08:00
|
|
|
measTargets = newTargets
|
2025-11-04 17:12:15 +08:00
|
|
|
|
2025-11-26 17:49:24 +08:00
|
|
|
if len(measTargets) == 0 {
|
|
|
|
|
delete(config.measurements, interval)
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-26 17:49:24 +08:00
|
|
|
if len(config.measurements) == 0 {
|
2025-11-11 17:37:06 +08:00
|
|
|
shouldRemoveClient = true
|
2025-11-06 17:22:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(targetsToRemoveMap) > 0 {
|
2025-11-11 17:37:06 +08:00
|
|
|
err := fmt.Errorf("target remove were not found under clientID %s and interval %s", clientID, interval)
|
2025-11-06 17:22:14 +08:00
|
|
|
for target := range targetsToRemoveMap {
|
|
|
|
|
targetResult := network.TargetResult{
|
|
|
|
|
ID: target,
|
|
|
|
|
Code: constants.CancelSubFailedCode,
|
|
|
|
|
Msg: fmt.Sprintf("%s: %s", constants.SubFailedMsg, err.Error()),
|
|
|
|
|
}
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-11 17:37:06 +08:00
|
|
|
config.mutex.Unlock()
|
2025-11-10 17:32:18 +08:00
|
|
|
// pass the removed subscription configuration to the notice channel
|
|
|
|
|
config.noticeChan <- transportTargets
|
2025-11-11 17:37:06 +08:00
|
|
|
|
|
|
|
|
if shouldRemoveClient {
|
|
|
|
|
s.globalMutex.Lock()
|
2025-11-26 17:49:24 +08:00
|
|
|
if currentConfig, exist := s.subMap[clientID]; exist && len(currentConfig.measurements) == 0 {
|
2025-11-13 11:48:26 +08:00
|
|
|
delete(s.subMap, clientID)
|
2025-11-11 17:37:06 +08:00
|
|
|
}
|
|
|
|
|
s.globalMutex.Unlock()
|
|
|
|
|
}
|
2025-11-06 17:22:14 +08:00
|
|
|
return targetProcessResults, nil
|
2025-11-04 17:12:15 +08:00
|
|
|
}
|
2025-11-05 18:20:54 +08:00
|
|
|
|
2025-12-02 17:26:15 +08:00
|
|
|
// UpdateTargets define function to update targets in SharedSubState
|
|
|
|
|
func (s *SharedSubState) UpdateTargets(ctx context.Context, tx *gorm.DB, clientID string, measurements []network.RealTimeMeasurementItem) ([]network.TargetResult, error) {
|
|
|
|
|
requestTargetsCount := processRealTimeRequestCount(measurements)
|
|
|
|
|
targetProcessResults := make([]network.TargetResult, 0, requestTargetsCount)
|
|
|
|
|
|
|
|
|
|
s.globalMutex.RLock()
|
|
|
|
|
config, exist := s.subMap[clientID]
|
|
|
|
|
s.globalMutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
if !exist {
|
|
|
|
|
s.globalMutex.RUnlock()
|
|
|
|
|
err := fmt.Errorf("clientID %s not found", clientID)
|
|
|
|
|
logger.Error(ctx, "clientID not found in remove targets operation", "error", err)
|
|
|
|
|
return processRealTimeRequestTargets(measurements, requestTargetsCount, err), err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetProcessResults, successfulTargets, newMeasMap, newMeasContextMap := processAndValidateTargetsForUpdate(ctx, tx, config, measurements, requestTargetsCount)
|
|
|
|
|
|
|
|
|
|
config.mutex.Lock()
|
|
|
|
|
allDuplicates, err := mergeMeasurementsForUpdate(config, newMeasMap, newMeasContextMap)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warn(ctx, "can not find exist measurements in target interval", "clientID", clientID, "duplicates", allDuplicates, "error", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(allDuplicates) > 0 {
|
|
|
|
|
logger.Warn(ctx, "some targets are duplicate and have been ignored in append operation", "clientID", clientID, "duplicates", allDuplicates)
|
|
|
|
|
// process repeat target in targetProcessResults and successfulTargets
|
|
|
|
|
targetProcessResults, successfulTargets = filterAndDeduplicateRepeatTargets(targetProcessResults, successfulTargets, allDuplicates)
|
|
|
|
|
}
|
|
|
|
|
config.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
if len(successfulTargets) > 0 {
|
|
|
|
|
transportTargets := &transportTargets{
|
|
|
|
|
OperationType: constants.OpUpdate,
|
|
|
|
|
Targets: successfulTargets,
|
|
|
|
|
}
|
|
|
|
|
config.noticeChan <- transportTargets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return targetProcessResults, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-13 17:29:49 +08:00
|
|
|
// Get define function to get subscriptions config from SharedSubState
|
|
|
|
|
func (s *SharedSubState) Get(clientID string) (*RealTimeSubConfig, bool) {
|
2025-11-11 17:37:06 +08:00
|
|
|
s.globalMutex.RLock()
|
|
|
|
|
defer s.globalMutex.RUnlock()
|
|
|
|
|
|
2025-11-13 11:48:26 +08:00
|
|
|
config, ok := s.subMap[clientID]
|
2025-11-11 17:37:06 +08:00
|
|
|
if !ok {
|
|
|
|
|
return nil, false
|
|
|
|
|
}
|
|
|
|
|
return config, true
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 16:59:03 +08:00
|
|
|
func processRealTimeRequestCount(measurements []network.RealTimeMeasurementItem) int {
|
2025-11-05 18:20:54 +08:00
|
|
|
totalTargetsCount := 0
|
2025-12-02 17:26:15 +08:00
|
|
|
for _, measItem := range measurements {
|
|
|
|
|
totalTargetsCount += len(measItem.Targets)
|
2025-11-05 18:20:54 +08:00
|
|
|
}
|
|
|
|
|
return totalTargetsCount
|
|
|
|
|
}
|
2025-11-06 17:22:14 +08:00
|
|
|
|
2025-11-27 16:59:03 +08:00
|
|
|
func processRealTimeRequestTargets(measurements []network.RealTimeMeasurementItem, targetCount int, err error) []network.TargetResult {
|
2025-11-06 17:22:14 +08:00
|
|
|
targetProcessResults := make([]network.TargetResult, 0, targetCount)
|
2025-11-27 16:59:03 +08:00
|
|
|
for _, measurementItem := range measurements {
|
|
|
|
|
for _, target := range measurementItem.Targets {
|
2025-11-06 17:22:14 +08:00
|
|
|
var targetResult network.TargetResult
|
|
|
|
|
targetResult.ID = target
|
|
|
|
|
targetResult.Code = constants.SubFailedCode
|
|
|
|
|
targetResult.Msg = fmt.Sprintf("%s: %s", constants.SubFailedMsg, err.Error())
|
|
|
|
|
targetProcessResults = append(targetProcessResults, targetResult)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return targetProcessResults
|
|
|
|
|
}
|
2025-11-10 17:32:18 +08:00
|
|
|
|
|
|
|
|
// transportTargets define struct to transport update or remove target to real
|
|
|
|
|
// time pull api
|
|
|
|
|
type transportTargets struct {
|
|
|
|
|
OperationType constants.TargetOperationType
|
|
|
|
|
Targets []string
|
|
|
|
|
}
|