Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package reporters
import (
"context"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// ReportNodeGroupService 监控节点分组
type ReportNodeGroupService struct {
services.BaseService
}
// CreateReportNodeGroup 创建分组
func (this *ReportNodeGroupService) CreateReportNodeGroup(ctx context.Context, req *pb.CreateReportNodeGroupRequest) (*pb.CreateReportNodeGroupResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
groupId, err := models.SharedReportNodeGroupDAO.CreateGroup(tx, req.Name)
if err != nil {
return nil, err
}
return &pb.CreateReportNodeGroupResponse{ReportNodeGroupId: groupId}, nil
}
// UpdateReportNodeGroup 修改分组
func (this *ReportNodeGroupService) UpdateReportNodeGroup(ctx context.Context, req *pb.UpdateReportNodeGroupRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedReportNodeGroupDAO.UpdateGroup(tx, req.ReportNodeGroupId, req.Name)
if err != nil {
return nil, err
}
return this.Success()
}
// DeleteReportNodeGroup 删除分组
func (this *ReportNodeGroupService) DeleteReportNodeGroup(ctx context.Context, req *pb.DeleteReportNodeGroupRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedReportNodeGroupDAO.DisableReportNodeGroup(tx, req.ReportNodeGroupId)
if err != nil {
return nil, err
}
return this.Success()
}
// FindAllEnabledReportNodeGroups 查找所有分组
func (this *ReportNodeGroupService) FindAllEnabledReportNodeGroups(ctx context.Context, req *pb.FindAllEnabledReportNodeGroupsRequest) (*pb.FindAllEnabledReportNodeGroupsResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
groups, err := models.SharedReportNodeGroupDAO.FindAllEnabledGroups(tx)
if err != nil {
return nil, err
}
var pbGroups = []*pb.ReportNodeGroup{}
for _, group := range groups {
pbGroups = append(pbGroups, &pb.ReportNodeGroup{
Id: int64(group.Id),
Name: group.Name,
IsOn: group.IsOn,
})
}
return &pb.FindAllEnabledReportNodeGroupsResponse{ReportNodeGroups: pbGroups}, nil
}
// FindEnabledReportNodeGroup 查找单个分组
func (this *ReportNodeGroupService) FindEnabledReportNodeGroup(ctx context.Context, req *pb.FindEnabledReportNodeGroupRequest) (*pb.FindEnabledReportNodeGroupResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
group, err := models.SharedReportNodeGroupDAO.FindEnabledReportNodeGroup(tx, req.ReportNodeGroupId)
if err != nil {
return nil, err
}
if group == nil {
return &pb.FindEnabledReportNodeGroupResponse{ReportNodeGroup: nil}, nil
}
return &pb.FindEnabledReportNodeGroupResponse{
ReportNodeGroup: &pb.ReportNodeGroup{
Id: int64(group.Id),
Name: group.Name,
IsOn: group.IsOn,
},
}, nil
}
// CountAllEnabledReportNodeGroups 计算所有分组数量
func (this *ReportNodeGroupService) CountAllEnabledReportNodeGroups(ctx context.Context, req *pb.CountAllEnabledReportNodeGroupsRequest) (*pb.RPCCountResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
count, err := models.SharedReportNodeGroupDAO.CountAllEnabledGroups(tx)
if err != nil {
return nil, err
}
return this.SuccessCount(count)
}

View File

@@ -0,0 +1,467 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build plus
// +build plus
package reporters
import (
"context"
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/iplibrary"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"google.golang.org/grpc/peer"
"net"
"time"
)
// ReportNodeService 监控终端服务
type ReportNodeService struct {
services.BaseService
}
// CreateReportNode 添加终端
func (this *ReportNodeService) CreateReportNode(ctx context.Context, req *pb.CreateReportNodeRequest) (*pb.CreateReportNodeResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
reporterId, err := models.SharedReportNodeDAO.CreateReportNode(tx, req.Name, req.Location, req.Isp, req.AllowIPs, req.ReportNodeGroupIds)
if err != nil {
return nil, err
}
return &pb.CreateReportNodeResponse{ReportNodeId: reporterId}, nil
}
// DeleteReportNode 删除终端
func (this *ReportNodeService) DeleteReportNode(ctx context.Context, req *pb.DeleteReportNodeRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedReportNodeDAO.DisableReportNode(tx, req.ReportNodeId)
if err != nil {
return nil, err
}
return this.Success()
}
// UpdateReportNode 修改终端
func (this *ReportNodeService) UpdateReportNode(ctx context.Context, req *pb.UpdateReportNodeRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedReportNodeDAO.UpdateReportNode(tx, req.ReportNodeId, req.Name, req.Location, req.Isp, req.AllowIPs, req.ReportNodeGroupIds, req.IsOn)
if err != nil {
return nil, err
}
return this.Success()
}
// CountAllEnabledReportNodes 计算终端数量
func (this *ReportNodeService) CountAllEnabledReportNodes(ctx context.Context, req *pb.CountAllEnabledReportNodesRequest) (*pb.RPCCountResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
count, err := models.SharedReportNodeDAO.CountAllEnabledReportNodes(tx, req.ReportNodeGroupId, req.Keyword)
if err != nil {
return nil, err
}
return this.SuccessCount(count)
}
// ListEnabledReportNodes 列出单页终端
func (this *ReportNodeService) ListEnabledReportNodes(ctx context.Context, req *pb.ListEnabledReportNodesRequest) (*pb.ListEnabledReportNodesResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
ones, err := models.SharedReportNodeDAO.ListEnabledReportNodes(tx, req.ReportNodeGroupId, req.Keyword, req.Offset, req.Size)
if err != nil {
return nil, err
}
var pbNodes = []*pb.ReportNode{}
for _, one := range ones {
var pbGroups = []*pb.ReportNodeGroup{}
var groupIds = one.DecodeGroupIds()
for _, groupId := range groupIds {
group, err := models.SharedReportNodeGroupDAO.FindEnabledReportNodeGroup(tx, groupId)
if err != nil {
return nil, err
}
if group == nil {
continue
}
pbGroups = append(pbGroups, &pb.ReportNodeGroup{
Id: int64(group.Id),
Name: group.Name,
IsOn: group.IsOn,
})
}
pbNodes = append(pbNodes, &pb.ReportNode{
Id: int64(one.Id),
UniqueId: one.UniqueId,
Secret: one.Secret,
IsOn: one.IsOn,
Name: one.Name,
Location: one.Location,
Isp: one.Isp,
IsActive: one.IsActive,
StatusJSON: one.Status,
AllowIPs: one.DecodeAllowIPs(),
ReportNodeGroups: pbGroups,
})
}
return &pb.ListEnabledReportNodesResponse{
ReportNodes: pbNodes,
}, nil
}
// FindEnabledReportNode 查找单个终端
func (this *ReportNodeService) FindEnabledReportNode(ctx context.Context, req *pb.FindEnabledReportNodeRequest) (*pb.FindEnabledReportNodeResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
node, err := models.SharedReportNodeDAO.FindEnabledReportNode(tx, req.ReportNodeId)
if err != nil {
return nil, err
}
if node == nil {
return &pb.FindEnabledReportNodeResponse{ReportNode: nil}, nil
}
var pbGroups = []*pb.ReportNodeGroup{}
var groupIds = node.DecodeGroupIds()
for _, groupId := range groupIds {
group, err := models.SharedReportNodeGroupDAO.FindEnabledReportNodeGroup(tx, groupId)
if err != nil {
return nil, err
}
if group == nil {
continue
}
pbGroups = append(pbGroups, &pb.ReportNodeGroup{
Id: int64(group.Id),
Name: group.Name,
IsOn: group.IsOn,
})
}
return &pb.FindEnabledReportNodeResponse{ReportNode: &pb.ReportNode{
Id: int64(node.Id),
UniqueId: node.UniqueId,
Secret: node.Secret,
IsOn: node.IsOn,
Name: node.Name,
Location: node.Location,
Isp: node.Isp,
IsActive: node.IsActive,
StatusJSON: node.Status,
AllowIPs: node.DecodeAllowIPs(),
ReportNodeGroups: pbGroups,
}}, nil
}
// UpdateReportNodeStatus 更新节点状态
func (this *ReportNodeService) UpdateReportNodeStatus(ctx context.Context, req *pb.UpdateReportNodeStatusRequest) (*pb.RPCSuccess, error) {
_, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeReport)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = validateClient(tx, nodeId, ctx)
if err != nil {
return nil, err
}
var status = &reporterconfigs.Status{}
err = json.Unmarshal(req.StatusJSON, status)
if err != nil {
return nil, err
}
status.UpdatedAt = time.Now().Unix()
p, ok := peer.FromContext(ctx)
if ok {
host, _, _ := net.SplitHostPort(p.Addr.String())
if len(host) > 0 {
status.IP = host
var result = iplibrary.LookupIP(host)
if result != nil && result.IsOk() {
status.Location = result.Summary()
status.ISP = result.ProviderName()
}
}
}
err = models.SharedReportNodeDAO.UpdateNodeStatus(tx, nodeId, status)
if err != nil {
return nil, err
}
return this.Success()
}
// FindCurrentReportNodeConfig 获取当前节点信息
func (this *ReportNodeService) FindCurrentReportNodeConfig(ctx context.Context, req *pb.FindCurrentReportNodeConfigRequest) (*pb.FindCurrentReportNodeConfigResponse, error) {
_, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeReport)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = validateClient(tx, nodeId, ctx)
if err != nil {
return nil, err
}
config, err := models.SharedReportNodeDAO.ComposeConfig(tx, nodeId)
if err != nil {
return nil, err
}
configJSON, err := json.Marshal(config)
if err != nil {
return nil, err
}
return &pb.FindCurrentReportNodeConfigResponse{ReportNodeJSON: configJSON}, nil
}
// FindReportNodeTasks 读取任务
func (this *ReportNodeService) FindReportNodeTasks(ctx context.Context, req *pb.FindReportNodeTasksRequest) (*pb.FindReportNodeTasksResponse, error) {
_, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeReport)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = validateClient(tx, nodeId, ctx)
if err != nil {
return nil, err
}
var result = &pb.FindReportNodeTasksResponse{}
var ipTasks = []*reporterconfigs.IPTask{}
// 所有的集群
// TODO 将来支持NS节点
clusters, err := models.SharedNodeClusterDAO.FindAllEnableClusters(tx)
if err != nil {
return nil, err
}
for _, cluster := range clusters {
if !cluster.IsOn {
continue
}
var clusterId = int64(cluster.Id)
port, err := models.SharedServerDAO.FindFirstHTTPOrHTTPSPortWithClusterId(tx, clusterId)
if err != nil {
return nil, err
}
if port <= 0 {
continue
}
// 读取所有IP地址
addrList, err := models.SharedNodeIPAddressDAO.FindAllAccessibleIPAddressesWithClusterId(tx, nodeconfigs.NodeRoleNode, clusterId, nil)
if err != nil {
return nil, err
}
for _, addr := range addrList {
if !addr.IsOn {
continue
}
var addrIP = addr.Ip
var backupIP = addr.DecodeBackupIP()
if len(backupIP) > 0 {
addrIP = backupIP
}
ipTasks = append(ipTasks, &reporterconfigs.IPTask{
AddrId: int64(addr.Id),
IP: addrIP,
Port: port,
})
}
}
ipTasksJSON, err := json.Marshal(ipTasks)
if err != nil {
return nil, err
}
result.IpAddrTasksJSON = ipTasksJSON
return result, nil
}
// FindLatestReportNodeVersion 取得最新的版本号
func (this *ReportNodeService) FindLatestReportNodeVersion(ctx context.Context, req *pb.FindLatestReportNodeVersionRequest) (*pb.FindLatestReportNodeVersionResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
return &pb.FindLatestReportNodeVersionResponse{
Version: teaconst.ReportNodeVersion,
}, nil
}
// CountAllReportNodeTasks 计算任务数量
func (this *ReportNodeService) CountAllReportNodeTasks(ctx context.Context, req *pb.CountAllReportNodeTasksRequest) (*pb.RPCCountResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var count int64
var tx *dbs.Tx
switch req.Type {
case reporterconfigs.TaskTypeIPAddr:
count, err = models.SharedNodeIPAddressDAO.CountAllAccessibleIPAddressesWithClusterId(tx, req.Role, req.NodeClusterId)
}
if err != nil {
return nil, err
}
return this.SuccessCount(count)
}
// ListReportNodeTasks 列出单页任务
func (this *ReportNodeService) ListReportNodeTasks(ctx context.Context, req *pb.ListReportNodeTasksRequest) (*pb.ListReportNodeTasksResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx *dbs.Tx
switch req.Type {
case reporterconfigs.TaskTypeIPAddr:
port, err := models.SharedServerDAO.FindFirstHTTPOrHTTPSPortWithClusterId(tx, req.NodeClusterId)
if err != nil {
return nil, err
}
addrs, err := models.SharedNodeIPAddressDAO.ListAccessibleIPAddressesWithClusterId(tx, req.Role, req.NodeClusterId, req.Offset, req.Size)
if err != nil {
return nil, err
}
var pbTasks = []*pb.IPAddrReportTask{}
for _, addr := range addrs {
var addrIP = addr.Ip
var backupIP = addr.DecodeBackupIP()
if len(backupIP) > 0 {
addrIP = backupIP
}
// 地址
var pbAddr = &pb.NodeIPAddress{
Id: int64(addr.Id),
NodeId: int64(addr.NodeId),
Name: addr.Name,
Ip: addrIP,
Description: addr.Description,
CanAccess: addr.CanAccess,
IsOn: addr.IsOn,
IsUp: addr.IsUp,
Role: addr.Role,
}
var connectivity = addr.DecodeConnectivity()
pbTasks = append(pbTasks, &pb.IPAddrReportTask{
Ip: addr.Ip,
Port: types.Int32(port),
NodeIPAddress: pbAddr,
CostMs: float32(connectivity.CostMs),
Level: connectivity.Level,
Connectivity: float32(connectivity.Percent),
})
}
return &pb.ListReportNodeTasksResponse{
IpAddrReportTasks: pbTasks,
}, nil
}
return &pb.ListReportNodeTasksResponse{}, nil
}
// UpdateReportNodeGlobalSetting 修改全局设置
func (this *ReportNodeService) UpdateReportNodeGlobalSetting(ctx context.Context, req *pb.UpdateReportNodeGlobalSetting) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
err = models.SharedSysSettingDAO.UpdateSetting(tx, systemconfigs.SettingCodeReportNodeGlobalSetting, req.SettingJSON)
if err != nil {
return nil, err
}
return this.Success()
}
// ReadReportNodeGlobalSetting 读取全局设置
func (this *ReportNodeService) ReadReportNodeGlobalSetting(ctx context.Context, req *pb.ReadReportNodeGlobalSettingRequest) (*pb.ReadReportNodeGlobalSettingResponse, error) {
_, _, err := this.ValidateNodeId(ctx, rpcutils.UserTypeAdmin, rpcutils.UserTypeReport)
if err != nil {
return nil, err
}
var tx = this.NullTx()
valueJSON, err := models.SharedSysSettingDAO.ReadSetting(tx, systemconfigs.SettingCodeReportNodeGlobalSetting)
if err != nil {
return nil, err
}
var setting = reporterconfigs.DefaultGlobalSetting()
if len(valueJSON) > 0 {
err = json.Unmarshal(valueJSON, setting)
if err != nil {
return nil, err
}
}
// 重新编码
valueJSON, err = json.Marshal(setting)
if err != nil {
return nil, err
}
return &pb.ReadReportNodeGlobalSettingResponse{
SettingJSON: valueJSON,
}, nil
}

View File

@@ -0,0 +1,335 @@
//go:build plus
// +build plus
package reporters
import (
"context"
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/configs"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"strconv"
"sync"
"sync/atomic"
"time"
)
// CommandRequest 命令请求相关
type CommandRequest struct {
Id int64
Code string
CommandJSON []byte
}
type CommandRequestWaiting struct {
Timestamp int64
Chan chan *pb.ReportNodeStreamMessage
}
func (this *CommandRequestWaiting) Close() {
defer func() {
_ = recover()
}()
close(this.Chan)
}
var responseChanMap = map[int64]*CommandRequestWaiting{} // request id => response
var commandRequestId = int64(0)
var nodeLocker = &sync.Mutex{}
var requestChanMap = map[int64]chan *CommandRequest{} // node id => chan
func NextCommandRequestId() int64 {
return atomic.AddInt64(&commandRequestId, 1)
}
func init() {
dbs.OnReadyDone(func() {
// 清理WaitingChannelMap
goman.New(func() {
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
nodeLocker.Lock()
for requestId, request := range responseChanMap {
if time.Now().Unix()-request.Timestamp > 3600 {
responseChanMap[requestId].Close()
delete(responseChanMap, requestId)
}
}
nodeLocker.Unlock()
}
})
// 自动同步连接到本API节点的Report节点任务
goman.New(func() {
defer func() {
_ = recover()
}()
// TODO 未来支持同步边缘节点
var ticker = time.NewTicker(3 * time.Second)
for range ticker.C {
nodeIds, err := models.SharedNodeTaskDAO.FindAllDoingNodeIds(nil, nodeconfigs.NodeRoleReport)
if err != nil {
remotelogs.Error("ReportNodeService_SYNC", err.Error())
continue
}
nodeLocker.Lock()
for _, nodeId := range nodeIds {
c, ok := requestChanMap[nodeId]
if ok {
select {
case c <- &CommandRequest{
Id: NextCommandRequestId(),
Code: reporterconfigs.MessageCodeNewNodeTask,
CommandJSON: nil,
}:
default:
}
}
}
nodeLocker.Unlock()
}
})
})
}
// ReportNodeStream 节点stream
func (this *ReportNodeService) ReportNodeStream(server pb.ReportNodeService_ReportNodeStreamServer) error {
// TODO 使用此stream快速通知Reporter节点更新
// 校验节点
_, nodeId, err := this.ValidateNodeId(server.Context(), rpcutils.UserTypeReport)
if err != nil {
return err
}
var tx = this.NullTx()
err = validateClient(tx, nodeId, server.Context())
if err != nil {
return err
}
// 返回连接成功
{
apiConfig, err := configs.SharedAPIConfig()
if err != nil {
return err
}
connectedMessage := &reporterconfigs.ConnectedAPINodeMessage{APINodeId: apiConfig.NumberId()}
connectedMessageJSON, err := json.Marshal(connectedMessage)
if err != nil {
return errors.Wrap(err)
}
err = server.Send(&pb.ReportNodeStreamMessage{
Code: reporterconfigs.MessageCodeConnectedAPINode,
DataJSON: connectedMessageJSON,
})
if err != nil {
return err
}
}
//logs.Println("[RPC]accepted ns node '" + types.String(nodeId) + "' connection")
// 标记为活跃状态
oldIsActive, err := models.SharedReportNodeDAO.FindNodeActive(tx, nodeId)
if err != nil {
return err
}
if !oldIsActive {
err = models.SharedReportNodeDAO.UpdateNodeActive(tx, nodeId, true)
if err != nil {
return err
}
// 发送恢复消息
nodeName, err := models.SharedReportNodeDAO.FindReportNodeName(tx, nodeId)
if err != nil {
return err
}
subject := "区域监控节点\"" + nodeName + "\"已经恢复在线"
msg := "区域监控节点\"" + nodeName + "\"已经恢复在线"
err = models.SharedMessageDAO.CreateNodeMessage(tx, nodeconfigs.NodeRoleReport, 0, nodeId, models.MessageTypeReportNodeActive, models.MessageLevelSuccess, subject, msg, nil, false)
if err != nil {
return err
}
}
nodeLocker.Lock()
requestChan, ok := requestChanMap[nodeId]
if !ok {
requestChan = make(chan *CommandRequest, 1024)
requestChanMap[nodeId] = requestChan
}
nodeLocker.Unlock()
defer func() {
nodeLocker.Lock()
delete(requestChanMap, nodeId)
nodeLocker.Unlock()
}()
// 发送请求
goman.New(func() {
for {
select {
case <-server.Context().Done():
return
case commandRequest := <-requestChan:
// logs.Println("[RPC]sending command '" + commandRequest.Code + "' to node '" + strconv.FormatInt(nodeId, 10) + "'")
retries := 3 // 错误重试次数
for i := 0; i < retries; i++ {
err := server.Send(&pb.ReportNodeStreamMessage{
RequestId: commandRequest.Id,
Code: commandRequest.Code,
DataJSON: commandRequest.CommandJSON,
})
if err != nil {
if i == retries-1 {
logs.Println("[RPC]send command '" + commandRequest.Code + "' failed: " + err.Error())
} else {
time.Sleep(1 * time.Second)
}
} else {
break
}
}
}
}
})
// 接受请求
for {
req, err := server.Recv()
if err != nil {
// 修改节点状态
err1 := models.SharedReportNodeDAO.UpdateNodeActive(tx, nodeId, false)
if err1 != nil {
logs.Println(err1.Error())
}
return err
}
func(req *pb.ReportNodeStreamMessage) {
// 因为 responseChan.Chan 有被关闭的风险所以我们使用recover防止panic
defer func() {
_ = recover()
}()
nodeLocker.Lock()
responseChan, ok := responseChanMap[req.RequestId]
if ok {
select {
case responseChan.Chan <- req:
default:
}
}
nodeLocker.Unlock()
}(req)
}
}
// SendCommandToReportNode 向节点发送命令
func (this *ReportNodeService) SendCommandToReportNode(ctx context.Context, req *pb.ReportNodeStreamMessage) (*pb.ReportNodeStreamMessage, error) {
// 校验请求
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
nodeId := req.ReportNodeId
if nodeId <= 0 {
return nil, errors.New("node id should not be less than 0")
}
nodeLocker.Lock()
requestChan, ok := requestChanMap[nodeId]
nodeLocker.Unlock()
if !ok {
return &pb.ReportNodeStreamMessage{
RequestId: req.RequestId,
IsOk: false,
Message: "node '" + strconv.FormatInt(nodeId, 10) + "' not connected yet",
}, nil
}
req.RequestId = NextCommandRequestId()
select {
case requestChan <- &CommandRequest{
Id: req.RequestId,
Code: req.Code,
CommandJSON: req.DataJSON,
}:
// 加入到等待队列中
respChan := make(chan *pb.ReportNodeStreamMessage, 1)
waiting := &CommandRequestWaiting{
Timestamp: time.Now().Unix(),
Chan: respChan,
}
nodeLocker.Lock()
responseChanMap[req.RequestId] = waiting
nodeLocker.Unlock()
// 等待响应
timeoutSeconds := req.TimeoutSeconds
if timeoutSeconds <= 0 {
timeoutSeconds = 10
}
timeout := time.NewTimer(time.Duration(timeoutSeconds) * time.Second)
select {
case resp := <-respChan:
// 从队列中删除
nodeLocker.Lock()
delete(responseChanMap, req.RequestId)
waiting.Close()
nodeLocker.Unlock()
if resp == nil {
return &pb.ReportNodeStreamMessage{
RequestId: req.RequestId,
Code: req.Code,
Message: "response timeout",
IsOk: false,
}, nil
}
return resp, nil
case <-timeout.C:
// 从队列中删除
nodeLocker.Lock()
delete(responseChanMap, req.RequestId)
waiting.Close()
nodeLocker.Unlock()
return &pb.ReportNodeStreamMessage{
RequestId: req.RequestId,
Code: req.Code,
Message: "response timeout over " + fmt.Sprintf("%d", timeoutSeconds) + " seconds",
IsOk: false,
}, nil
}
default:
return &pb.ReportNodeStreamMessage{
RequestId: req.RequestId,
Code: req.Code,
Message: "command queue is full over " + strconv.Itoa(len(requestChan)),
IsOk: false,
}, nil
}
}

View File

@@ -0,0 +1,237 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package reporters
import (
"context"
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/http"
"strings"
"time"
)
// ReportResultService 区域监控报告结果
type ReportResultService struct {
services.BaseService
}
// CountAllReportResults 计算监控结果数量
func (this *ReportResultService) CountAllReportResults(ctx context.Context, req *pb.CountAllReportResultsRequest) (*pb.RPCCountResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
count, err := models.SharedReportResultDAO.CountAllResults(tx, req.ReportNodeId, req.Level, types.Int8(req.OkState))
if err != nil {
return nil, err
}
return this.SuccessCount(count)
}
// ListReportResults 列出单页监控结果
func (this *ReportResultService) ListReportResults(ctx context.Context, req *pb.ListReportResultsRequest) (*pb.ListReportResultsResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
results, err := models.SharedReportResultDAO.ListResults(tx, req.ReportNodeId, types.Int8(req.OkState), req.Level, req.Offset, req.Size)
if err != nil {
return nil, err
}
var pbResults = []*pb.ReportResult{}
for _, result := range results {
pbResults = append(pbResults, &pb.ReportResult{
Id: int64(result.Id),
Type: result.Type,
TargetId: int64(result.TargetId),
TargetDesc: result.TargetDesc,
ReportNodeId: int64(result.ReportNodeId),
IsOk: result.IsOk,
CostMs: float32(result.CostMs),
Error: result.Error,
UpdatedAt: int64(result.UpdatedAt),
Level: result.Level,
})
}
return &pb.ListReportResultsResponse{
ReportResults: pbResults,
}, nil
}
// UpdateReportResults 上传报告结果
func (this *ReportResultService) UpdateReportResults(ctx context.Context, req *pb.UpdateReportResultsRequest) (*pb.RPCSuccess, error) {
_, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeReport)
if err != nil {
return nil, err
}
if !teaconst.IsPlus {
return nil, errors.New("the commercial version is expired.")
}
var tx = this.NullTx()
err = validateClient(tx, nodeId, ctx)
if err != nil {
return nil, err
}
// 设置
var setting = reporterconfigs.DefaultGlobalSetting()
settingJSON, err := models.SharedSysSettingDAO.ReadSetting(tx, systemconfigs.SettingCodeReportNodeGlobalSetting)
if err != nil {
return nil, err
}
if len(settingJSON) > 0 {
err = json.Unmarshal(settingJSON, setting)
if err != nil {
return nil, err
}
}
for _, result := range req.ReportResults {
// 更新数据
err := models.SharedReportResultDAO.UpdateResult(tx, result.Type, result.TargetId, result.TargetDesc, nodeId, result.Level, result.IsOk, float64(result.CostMs), result.Error)
if err != nil {
return nil, err
}
// 更新对象状态
costMs, err := models.SharedReportResultDAO.FindAvgCostMsWithTarget(tx, result.Type, result.TargetId)
if err != nil {
return nil, err
}
level, err := models.SharedReportResultDAO.FindAvgLevelWithTarget(tx, result.Type, result.TargetId)
if err != nil {
return nil, err
}
percent, err := models.SharedReportResultDAO.FindConnectivityWithTargetPercent(tx, result.Type, result.TargetId, 0)
if err != nil {
return nil, err
}
// 是否应该通知
if setting != nil && percent < setting.MinNotifyConnectivity {
switch result.Type {
case reporterconfigs.TaskTypeIPAddr:
addr, err := models.SharedNodeIPAddressDAO.FindEnabledAddress(tx, result.TargetId)
if err != nil {
return nil, err
}
if addr != nil {
var nodeId = int64(addr.NodeId)
clusterId, err := models.SharedNodeDAO.FindNodeClusterId(tx, nodeId)
if err != nil {
return nil, err
}
var messageSubject = "IP地址" + addr.Ip + "连通性低于" + types.String(setting.MinNotifyConnectivity) + "%"
err = models.SharedMessageDAO.CreateNodeMessage(tx, addr.Role, clusterId, nodeId, models.MessageTypeConnectivity, models.LevelError, messageSubject, messageSubject, maps.Map{"addrId": addr.Id}.AsJSON(), false)
if err != nil {
return nil, err
}
err = models.SharedMessageTaskDAO.CreateMessageTasks(tx, addr.Role, clusterId, nodeId, 0, models.MessageTypeConnectivity, messageSubject, messageSubject)
if err != nil {
return nil, err
}
// 发送外部通知
if len(setting.NotifyWebHookURL) > 0 {
var client = utils.SharedHttpClient(10 * time.Second)
var url = setting.NotifyWebHookURL
var args = "role=" + addr.Role + "&clusterId=" + types.String(clusterId) + "&nodeId=" + types.String(nodeId) + "&addressId=" + types.String(addr.Id) + "&ip=" + addr.Ip
var hasQuestionMark = strings.Contains(url, "?")
if hasQuestionMark {
url += "&" + args
} else {
url += "?" + args
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
// 不阻断执行
remotelogs.Error("ReportResultService.UpdateReportResults", "notify url '"+url+"' failed: "+err.Error())
} else {
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
resp, err := client.Do(req)
if err != nil {
// 不阻断执行
remotelogs.Error("ReportResultService.UpdateReportResults", "notify url '"+url+"' failed: "+err.Error())
} else {
_ = resp.Body.Close()
}
}
}
}
}
}
// 保存
switch result.Type {
case reporterconfigs.TaskTypeIPAddr:
err = models.SharedNodeIPAddressDAO.UpdateAddressConnectivity(tx, result.TargetId, &nodeconfigs.Connectivity{
CostMs: costMs,
Level: level,
Percent: percent * 100,
UpdatedAt: time.Now().Unix(),
})
if err != nil {
return nil, err
}
}
}
return this.Success()
}
// FindAllReportResults 查询某个对象的监控结果
func (this *ReportResultService) FindAllReportResults(ctx context.Context, req *pb.FindAllReportResultsRequest) (*pb.FindAllReportResultsResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var tx = this.NullTx()
results, err := models.SharedReportResultDAO.FindAllResults(tx, req.Type, req.TargetId)
if err != nil {
return nil, err
}
var pbResults = []*pb.ReportResult{}
for _, result := range results {
pbResults = append(pbResults, &pb.ReportResult{
Id: int64(result.Id),
Type: result.Type,
TargetId: int64(result.TargetId),
TargetDesc: result.TargetDesc,
ReportNodeId: int64(result.ReportNodeId),
IsOk: result.IsOk,
CostMs: float32(result.CostMs),
Error: result.Error,
UpdatedAt: int64(result.UpdatedAt),
Level: result.Level,
})
}
return &pb.FindAllReportResultsResponse{
ReportResults: pbResults,
}, nil
}

View File

@@ -0,0 +1,40 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package reporters
import (
"context"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/dbs"
"google.golang.org/grpc/peer"
"net"
)
// 校验客户端IP
func validateClient(tx *dbs.Tx, nodeId int64, ctx context.Context) error {
allowIPs, err := models.SharedReportNodeDAO.FindNodeAllowIPs(tx, nodeId)
if err != nil {
return err
}
if len(allowIPs) == 0 {
return nil
}
p, ok := peer.FromContext(ctx)
if ok {
host, _, _ := net.SplitHostPort(p.Addr.String())
if len(host) > 0 {
for _, ip := range allowIPs {
r, err := shared.ParseIPRange(ip)
if err == nil && r != nil {
if r.Contains(host) {
return nil
}
}
}
}
}
return errors.New("client was not allowed")
}