880 lines
24 KiB
Go
880 lines
24 KiB
Go
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||
//go:build plus
|
||
|
||
package models
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
|
||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||
"github.com/TeaOSLab/EdgeAPI/internal/utils/regexputils"
|
||
"github.com/TeaOSLab/EdgeAPI/internal/zero"
|
||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||
"github.com/iwind/TeaGo/dbs"
|
||
"github.com/iwind/TeaGo/lists"
|
||
"github.com/iwind/TeaGo/maps"
|
||
"github.com/iwind/TeaGo/types"
|
||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||
"net/http"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// 从集群加载其他服务
|
||
func (this *NodeDAO) loadServersFromCluster(tx *dbs.Tx, clusterId int64, serverIdMap map[int64]zero.Zero) ([]*Server, error) {
|
||
// 高防实例
|
||
// TODO 将来支持部署到具体节点
|
||
objectCodes, err := SharedADPackageInstanceDAO.FindAvailableObjectCodesInCluster(tx, clusterId)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var resultServers = []*Server{}
|
||
for _, objectCode := range objectCodes {
|
||
objectType, objectValue := SharedUserADInstanceDAO.SplitObjectCode(objectCode)
|
||
if objectType == ADObjectTypeServer && len(objectValue) > 0 {
|
||
var serverId = types.Int64(objectValue)
|
||
if serverId > 0 {
|
||
_, ok := serverIdMap[serverId]
|
||
if !ok {
|
||
serverIdMap[serverId] = zero.Zero{}
|
||
|
||
// TODO 将来支持一次性读取一批server
|
||
server, err := SharedServerDAO.FindEnabledServer(tx, serverId)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if server != nil && server.IsOn {
|
||
resultServers = append(resultServers, server)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return resultServers, nil
|
||
}
|
||
|
||
// 组合扩展配置
|
||
func (this *NodeDAO) composeExtConfig(tx *dbs.Tx, config *nodeconfigs.NodeConfig, clusterIds []int64, cacheMap *utils.CacheMap) error {
|
||
// 脚本
|
||
// scripts
|
||
scriptConfigs, err := SharedScriptHistoryDAO.ComposeScriptConfigs(tx, 0, cacheMap)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
config.CommonScripts = scriptConfigs
|
||
|
||
// 套餐
|
||
// plan
|
||
plans, err := SharedPlanDAO.FindAllAvailableBasicPlans(tx)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
var planMap = map[int64]*serverconfigs.PlanConfig{}
|
||
for _, plan := range plans {
|
||
planMap[int64(plan.Id)] = &serverconfigs.PlanConfig{
|
||
Id: int64(plan.Id),
|
||
Name: plan.Name,
|
||
BandwidthLimitPerNode: plan.DecodeBandwidthLimitPerNode(),
|
||
TrafficLimit: plan.DecodeTrafficLimit(),
|
||
MaxUploadSize: plan.DecodeMaxUploadSize(),
|
||
}
|
||
}
|
||
config.Plans = planMap
|
||
|
||
// 父节点
|
||
// 这里不需要进行 teaconst.IsPlus 判断,是为了防止在API节点升级过程中或者用户授权过期又激活时,节点无法正常更新
|
||
if config.Level == 1 {
|
||
parentNodes, err := SharedNodeDAO.FindParentNodeConfigs(tx, config.Id, config.GroupId, clusterIds, types.Int(config.Level))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
config.ParentNodes = parentNodes
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// UpdateNodeSchedule 修改智能调度信息
|
||
// offlineDay YYYYMMDD
|
||
func (this *NodeDAO) UpdateNodeSchedule(tx *dbs.Tx, nodeId int64, offlineDay string, isBackupForCluster bool, isBackupForGroup bool, backupIPs []string) error {
|
||
if nodeId <= 0 {
|
||
return errors.New("invalid nodeId")
|
||
}
|
||
|
||
var op = NewNodeOperator()
|
||
op.Id = nodeId
|
||
|
||
if len(offlineDay) > 0 && !regexputils.YYYYMMDD.MatchString(offlineDay) {
|
||
return errors.New("invalid 'offlineDay' value: " + offlineDay)
|
||
}
|
||
op.OfflineDay = offlineDay
|
||
if offlineDay >= timeutil.Format("Ymd") {
|
||
op.OfflineIsNotified = false
|
||
}
|
||
|
||
op.IsBackupForCluster = isBackupForCluster
|
||
op.IsBackupForGroup = isBackupForGroup
|
||
|
||
if backupIPs == nil {
|
||
backupIPs = []string{}
|
||
}
|
||
backupIPsJSON, err := json.Marshal(backupIPs)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
op.BackupIPs = backupIPsJSON
|
||
err = this.Save(tx, op)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return this.NotifyDNSUpdate(tx, nodeId)
|
||
}
|
||
|
||
// FindAllNodeScheduleWithClusterId 查找集群下的节点
|
||
func (this *NodeDAO) FindAllNodeScheduleWithClusterId(tx *dbs.Tx, clusterId int64) (result []*Node, err error) {
|
||
if clusterId <= 0 {
|
||
return
|
||
}
|
||
_, err = this.Query(tx).
|
||
Result("id", "name", "groupId", "offlineDay", "backupIPs", "isBackupForCluster", "isBackupForGroup", "actionStatus").
|
||
Attr("clusterId", clusterId).
|
||
State(NodeStateEnabled).
|
||
Slice(&result).
|
||
FindAll()
|
||
return
|
||
}
|
||
|
||
// FindNodeSchedule 查找智能调度信息
|
||
func (this *NodeDAO) FindNodeSchedule(tx *dbs.Tx, nodeId int64) (node *Node, err error) {
|
||
if nodeId <= 0 {
|
||
err = errors.New("invalid nodeId")
|
||
return
|
||
}
|
||
|
||
one, err := this.Query(tx).
|
||
Result("id", "offlineDay", "isBackupForCluster", "isBackupForGroup", "backupIPs", "actionStatus").
|
||
Pk(nodeId).
|
||
Find()
|
||
if err != nil || one == nil {
|
||
return nil, err
|
||
}
|
||
|
||
return one.(*Node), nil
|
||
}
|
||
|
||
// NotifyNodesWithOfflineDay 通知租期到期的节点
|
||
var notifiedOfflineDayMap = map[string]bool{} // DAY_{END|00}
|
||
var notifiedOfflineDayLocker = &sync.Mutex{}
|
||
|
||
func (this *NodeDAO) NotifyNodesWithOfflineDay(tx *dbs.Tx) error {
|
||
// 每天最多两次
|
||
var cacheKey = timeutil.Format("Ymd")
|
||
var endHour = 23
|
||
if time.Now().Hour() == endHour {
|
||
cacheKey += "_END"
|
||
} else {
|
||
cacheKey += "_00"
|
||
}
|
||
|
||
notifiedOfflineDayLocker.Lock()
|
||
if notifiedOfflineDayMap[cacheKey] {
|
||
notifiedOfflineDayLocker.Unlock()
|
||
return nil
|
||
}
|
||
notifiedOfflineDayLocker.Unlock()
|
||
|
||
var query = this.Query(tx).
|
||
State(NodeStateEnabled).
|
||
Result("id", "clusterId", "offlineDay").
|
||
Attr("offlineIsNotified", false)
|
||
|
||
if time.Now().Hour() == endHour {
|
||
// 提前将今天到期的也下线
|
||
query.
|
||
Where("(LENGTH(offlineDay) > 0 AND offlineDay<=:offlineDay)").
|
||
Param("offlineDay", timeutil.Format("Ymd"))
|
||
} else {
|
||
// 将以往的下线
|
||
query.
|
||
Where("(LENGTH(offlineDay) > 0 AND offlineDay<:offlineDay)").
|
||
Param("offlineDay", timeutil.Format("Ymd"))
|
||
}
|
||
|
||
nodeOnes, err := query.
|
||
FindAll()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
var clusterIdMap = map[int64]bool{} // clusterId => bool
|
||
for _, nodeOne := range nodeOnes {
|
||
var node = nodeOne.(*Node)
|
||
var offlineDay = node.OfflineDay
|
||
if len(offlineDay) != 8 {
|
||
continue
|
||
}
|
||
var realOfflineDay = offlineDay[:4] + "-" + offlineDay[4:6] + "-" + offlineDay[6:]
|
||
|
||
var nodeId = int64(node.Id)
|
||
var clusterId = int64(node.ClusterId)
|
||
if clusterId > 0 && !clusterIdMap[clusterId] {
|
||
err = dns.SharedDNSTaskDAO.CreateClusterTask(tx, clusterId, dns.DNSTaskTypeClusterNodesChange)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
clusterIdMap[clusterId] = true
|
||
}
|
||
|
||
// 设置为已通知
|
||
err = this.Query(tx).
|
||
Pk(nodeId).
|
||
Set("offlineIsNotified", true).
|
||
UpdateQuickly()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 发送消息
|
||
err = SharedMessageDAO.CreateNodeMessage(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, MessageTypeNodeOfflineDay, MessageLevelError, "节点租期已结束("+realOfflineDay+")", "节点租期已结束("+realOfflineDay+")", nil, true)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
notifiedOfflineDayLocker.Lock()
|
||
notifiedOfflineDayMap[cacheKey] = true
|
||
notifiedOfflineDayLocker.Unlock()
|
||
|
||
return nil
|
||
}
|
||
|
||
// ResetNodeActionStatus 重置节点动作状态
|
||
// checkConds 是否检查条件
|
||
func (this *NodeDAO) ResetNodeActionStatus(tx *dbs.Tx, nodeId int64, checkConds bool) error {
|
||
if nodeId <= 0 {
|
||
return errors.New("invalid 'nodeId'")
|
||
}
|
||
|
||
nodeOne, err := this.Query(tx).
|
||
Pk(nodeId).
|
||
Result("clusterId", "actionStatus").
|
||
Find()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if nodeOne == nil {
|
||
return nil
|
||
}
|
||
var node = nodeOne.(*Node)
|
||
var clusterId = int64(node.ClusterId)
|
||
if clusterId <= 0 {
|
||
return nil
|
||
}
|
||
|
||
if len(node.ActionStatus) == 0 {
|
||
return nil
|
||
}
|
||
|
||
if checkConds {
|
||
var actionStatus = &nodeconfigs.NodeActionStatus{}
|
||
err = json.Unmarshal(node.ActionStatus, actionStatus)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 处理健康检查的特殊情况
|
||
if actionStatus.ActionId > 0 {
|
||
upAddressId, err := SharedNodeIPAddressDAO.FindFirstNodeAccessIPAddressId(tx, nodeId, true, nodeconfigs.NodeRoleNode)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if upAddressId <= 0 {
|
||
if actionStatus.DurationSeconds <= 0 || actionStatus.DurationSeconds > 3600 {
|
||
actionStatus.DurationSeconds = 3600
|
||
}
|
||
actionStatus.ExpiresAt += actionStatus.DurationSeconds
|
||
actionStatusJSON, err := json.Marshal(actionStatus)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
err = this.Query(tx).
|
||
Pk(nodeId).
|
||
Set("actionStatus", actionStatusJSON).
|
||
UpdateQuickly()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 发送通知
|
||
return SharedMessageDAO.CreateNodeMessage(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, MessageTypeNodeSchedule, MessageLevelError, "节点未能从智能调度状态中恢复", "节点由于没有可以正常访问的IP,所以未能从智能调度状态中恢复,将在下次循环中再次尝试。", nil, true)
|
||
}
|
||
}
|
||
}
|
||
|
||
err = this.Query(tx).
|
||
Pk(nodeId).
|
||
Set("actionStatus", dbs.SQL("NULL")).
|
||
UpdateQuickly()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
err = dns.SharedDNSTaskDAO.CreateClusterTask(tx, clusterId, dns.DNSTaskTypeClusterNodesChange)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 发送通知
|
||
return SharedMessageDAO.CreateNodeMessage(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, MessageTypeNodeSchedule, MessageLevelSuccess, "节点已从智能调度状态中恢复", "节点已从智能调度状态中恢复。", nil, true)
|
||
}
|
||
|
||
// FindNodeIdsWithExpiredActions 查找动作状态已过期的节点ID
|
||
func (this *NodeDAO) FindNodeIdsWithExpiredActions(tx *dbs.Tx) (nodeIds []int64, err error) {
|
||
ones, err := this.Query(tx).
|
||
Result("id").
|
||
State(NodeStateEnabled).
|
||
Where("(actionStatus IS NOT NULL AND JSON_EXTRACT(actionStatus, '$.expiresAt')<:currentTime)").
|
||
Param("currentTime", time.Now().Unix()).
|
||
FindAll()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for _, one := range ones {
|
||
nodeIds = append(nodeIds, int64(one.(*Node).Id))
|
||
}
|
||
|
||
return
|
||
}
|
||
|
||
// CopyNodeActionsToGroup 复制动作设置到分组
|
||
func (this *NodeDAO) CopyNodeActionsToGroup(tx *dbs.Tx, nodeId int64) error {
|
||
groupId, err := this.Query(tx).
|
||
Pk(nodeId).
|
||
Result("groupId").
|
||
FindInt64Col(0)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if groupId <= 0 {
|
||
return nil
|
||
}
|
||
|
||
const nodeRole = nodeconfigs.NodeRoleNode
|
||
|
||
actions, err := SharedNodeActionDAO.FindAllNodeActions(tx, nodeRole, nodeId)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
nodeOnes, err := this.Query(tx).
|
||
ResultPk().
|
||
Attr("groupId", groupId).
|
||
Neq("id", nodeId).
|
||
State(NodeStateEnabled).
|
||
FindAll()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, nodeOne := range nodeOnes {
|
||
var targetNodeId = int64(nodeOne.(*Node).Id)
|
||
err = SharedNodeActionDAO.DisableAllNodeActions(tx, nodeRole, targetNodeId)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, action := range actions {
|
||
err = SharedNodeActionDAO.DuplicateNodeAction(tx, action, nodeRole, targetNodeId)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CopyNodeActionsToCluster 复制动作设置到集群
|
||
func (this *NodeDAO) CopyNodeActionsToCluster(tx *dbs.Tx, nodeId int64) error {
|
||
clusterId, err := this.Query(tx).
|
||
Pk(nodeId).
|
||
Result("clusterId").
|
||
FindInt64Col(0)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if clusterId <= 0 {
|
||
return nil
|
||
}
|
||
|
||
const nodeRole = nodeconfigs.NodeRoleNode
|
||
|
||
actions, err := SharedNodeActionDAO.FindAllNodeActions(tx, nodeRole, nodeId)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
nodeOnes, err := this.Query(tx).
|
||
ResultPk().
|
||
Attr("clusterId", clusterId).
|
||
Neq("id", nodeId).
|
||
State(NodeStateEnabled).
|
||
FindAll()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, nodeOne := range nodeOnes {
|
||
var targetNodeId = int64(nodeOne.(*Node).Id)
|
||
|
||
err = SharedNodeActionDAO.DisableAllNodeActions(tx, nodeRole, targetNodeId)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, action := range actions {
|
||
err = SharedNodeActionDAO.DuplicateNodeAction(tx, action, nodeRole, targetNodeId)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// FireNodeActions 触发动作
|
||
func (this *NodeDAO) FireNodeActions(tx *dbs.Tx, nodeId int64, paramCode nodeconfigs.NodeActionParam, paramValue any) error {
|
||
// 只有Plus会员才会触发
|
||
if !teaconst.IsPlus {
|
||
return nil
|
||
}
|
||
|
||
nodeOne, err := this.Query(tx).
|
||
Pk(nodeId).
|
||
Result("isBackupForCluster", "isBackupForGroup", "actionStatus", "offlineDay").
|
||
State(NodeStateEnabled).
|
||
Find()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if nodeOne == nil {
|
||
return nil
|
||
}
|
||
var node = nodeOne.(*Node)
|
||
|
||
// 备用节点不处理
|
||
if node.IsBackupForCluster || node.IsBackupForGroup {
|
||
return nil
|
||
}
|
||
|
||
// 是否已下线
|
||
if node.CheckIsOffline() {
|
||
return nil
|
||
}
|
||
|
||
// 当前节点是否已经在动作状态中
|
||
var statusJSON = node.ActionStatus
|
||
if len(statusJSON) == 0 {
|
||
return this.fireNodeActions(tx, nodeId, paramCode, paramValue)
|
||
}
|
||
|
||
var status = &nodeconfigs.NodeActionStatus{}
|
||
err = json.Unmarshal(statusJSON, status)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if status.ActionId <= 0 {
|
||
return this.fireNodeActions(tx, nodeId, paramCode, paramValue)
|
||
}
|
||
|
||
// 如果现在已经处于切换状态,则不做任何处理(使用任务来处理)
|
||
|
||
return nil
|
||
}
|
||
|
||
// 触发某个参数相关的动作
|
||
func (this *NodeDAO) fireNodeActions(tx *dbs.Tx, nodeId int64, paramCode nodeconfigs.NodeActionParam, paramValue any) error {
|
||
actions, err := SharedNodeActionDAO.FindAllAvailableNodeActionsWithParam(tx, nodeconfigs.NodeRoleNode, nodeId, paramCode)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
if len(actions) == 0 {
|
||
return nil
|
||
}
|
||
|
||
for _, action := range actions {
|
||
var condsConfig = action.DecodeCondsConfig()
|
||
b, err := condsConfig.Match(func(param nodeconfigs.NodeActionParam) (value any, err error) {
|
||
if paramCode == param && paramValue != nil {
|
||
return paramValue, nil
|
||
}
|
||
|
||
switch param {
|
||
case nodeconfigs.NodeActionParamDailyTrafficOut:
|
||
var today = timeutil.Format("Ymd")
|
||
stat, err := SharedNodeTrafficDailyStatDAO.SumDailyStat(tx, nodeconfigs.NodeRoleNode, nodeId, today, today)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if stat != nil {
|
||
value = stat.Bytes
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamMonthlyTrafficOut:
|
||
var monthBegin = timeutil.Format("Ym01")
|
||
var today = timeutil.Format("Ymd")
|
||
stat, err := SharedNodeTrafficDailyStatDAO.SumDailyStat(tx, nodeconfigs.NodeRoleNode, nodeId, monthBegin, today)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if stat != nil {
|
||
value = stat.Bytes
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamBandwidthIn:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemAllTraffic)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetInt64("avgInBytes") * 8
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamBandwidthOut:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemAllTraffic)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetInt64("avgOutBytes") * 8
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamUDPDPSIn:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemAllTraffic)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetInt64("avgUDPInDatagrams")
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamUDPDPSOut:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemAllTraffic)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetInt64("avgUDPOutDatagrams")
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamCPUUsage:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemCPU)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetFloat64("usage") * 100
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamMemoryUsage:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemMemory)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetFloat64("usage") * 100
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamLoad:
|
||
itemValue, err := SharedNodeValueDAO.FindLatestNodeValue(tx, nodeconfigs.NodeRoleNode, nodeId, nodeconfigs.NodeValueItemLoad)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if itemValue == nil {
|
||
return 0, nil
|
||
}
|
||
var mapValue = itemValue.DecodeMapValue()
|
||
if mapValue != nil {
|
||
value = mapValue.GetInt64("load5m")
|
||
} else {
|
||
value = 0
|
||
}
|
||
case nodeconfigs.NodeActionParamHealthCheckFailure:
|
||
// 不需要返回值
|
||
|
||
}
|
||
return
|
||
})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if b {
|
||
return this.runAction(tx, nodeId, int64(action.Id), condsConfig, action.DecodeAction(), action.DecodeDuration())
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// 执行动作
|
||
func (this *NodeDAO) runAction(tx *dbs.Tx, nodeId int64, actionId int64, condsConfig *nodeconfigs.NodeActionCondsConfig, actionConfig *nodeconfigs.NodeActionConfig, duration *shared.TimeDuration) error {
|
||
if actionConfig == nil {
|
||
return errors.New("'actionConfig' should not be nil")
|
||
}
|
||
|
||
// 暂时不处理secondary clusters
|
||
clusterId, err := this.Query(tx).
|
||
Result("clusterId").
|
||
Pk(nodeId).
|
||
FindInt64Col(0)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if clusterId == 0 {
|
||
return nil
|
||
}
|
||
|
||
var status = &nodeconfigs.NodeActionStatus{
|
||
ActionId: actionId,
|
||
Conds: condsConfig,
|
||
Action: actionConfig,
|
||
CreatedAt: time.Now().Unix(),
|
||
}
|
||
|
||
if duration != nil {
|
||
var seconds = duration.Seconds()
|
||
if seconds > 0 {
|
||
status.ExpiresAt = time.Now().Unix() + seconds
|
||
status.DurationSeconds = seconds
|
||
}
|
||
}
|
||
if status.ExpiresAt <= 0 {
|
||
status.ExpiresAt = time.Now().Unix() + 3600 // 默认1个小时
|
||
}
|
||
if status.DurationSeconds <= 0 {
|
||
status.DurationSeconds = 3600
|
||
}
|
||
|
||
// 特殊情况下的过期时间
|
||
if condsConfig != nil {
|
||
var now = time.Now()
|
||
for _, cond := range condsConfig.Conds {
|
||
if cond.Param == nodeconfigs.NodeActionParamDailyTrafficOut {
|
||
// 当天结束
|
||
var expiresAt = now.Unix() - int64(now.Hour()*3600) - int64(now.Minute()*60) - int64(now.Second()) + 86400
|
||
if expiresAt > status.ExpiresAt {
|
||
status.ExpiresAt = expiresAt
|
||
}
|
||
} else if cond.Param == nodeconfigs.NodeActionParamMonthlyTrafficOut {
|
||
// 当月结束
|
||
var endDay = 32 - time.Date(now.Year(), now.Month(), 32, 0, 0, 0, 0, time.Local).Day()
|
||
|
||
var expiresAt = now.Unix() - int64(now.Hour()*3600) - int64(now.Minute()*60) - int64(now.Second()) + int64((endDay-now.Day()+1)*86400)
|
||
if expiresAt > status.ExpiresAt {
|
||
status.ExpiresAt = expiresAt
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
statusJSON, err := json.Marshal(status)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
err = this.Query(tx).
|
||
Pk(nodeId).
|
||
Set("actionStatus", statusJSON).
|
||
UpdateQuickly()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
switch actionConfig.Code {
|
||
case nodeconfigs.NodeActionCodeWebHook:
|
||
var url = maps.NewMap(actionConfig.Params).GetString("url")
|
||
if len(url) > 0 {
|
||
if strings.Contains(url, "?") {
|
||
url += "&"
|
||
} else {
|
||
url += "?"
|
||
}
|
||
url += "role=node&clusterId=" + types.String(clusterId) + "&nodeId=" + types.String(nodeId)
|
||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
var httpClient = utils.SharedHttpClient(5 * time.Second)
|
||
httpResp, httpErr := httpClient.Do(req)
|
||
if httpErr == nil {
|
||
_ = httpResp.Body.Close()
|
||
}
|
||
}
|
||
default:
|
||
// 通知更新
|
||
err = dns.SharedDNSTaskDAO.CreateClusterTask(tx, clusterId, dns.DNSTaskTypeClusterNodesChange)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 发送消息
|
||
return SharedMessageDAO.CreateNodeMessage(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, MessageTypeNodeSchedule, MessageLevelError, "节点已满足调度条件", "节点已满足调度条件,并执行动作:"+nodeconfigs.FindNodeActionName(actionConfig.Code), nil, true)
|
||
}
|
||
|
||
// CheckNodeIPAddresses 检查节点IP地址
|
||
func (this *NodeDAO) CheckNodeIPAddresses(tx *dbs.Tx, node *Node) (shouldSkip bool, shouldOverwrite bool, ipAddressStrings []string, err error) {
|
||
// 是否到期
|
||
if len(node.OfflineDay) > 0 && node.OfflineDay < timeutil.Format("Ymd") {
|
||
// 到期后直接跳过
|
||
shouldSkip = true
|
||
return
|
||
}
|
||
|
||
// 是否为备用
|
||
if node.IsBackupForCluster || node.IsBackupForGroup {
|
||
shouldSkip = true
|
||
return
|
||
}
|
||
|
||
// 当前状态
|
||
if len(node.ActionStatus) > 0 {
|
||
var actionStatus = node.DecodeActionStatus()
|
||
if actionStatus.ActionId > 0 && actionStatus.Action != nil {
|
||
switch actionStatus.Action.Code {
|
||
case nodeconfigs.NodeActionCodeUp:
|
||
// do nothing
|
||
return
|
||
case nodeconfigs.NodeActionCodeDown:
|
||
shouldSkip = true
|
||
return
|
||
case nodeconfigs.NodeActionCodeSwitchToBackupNodesInCluster:
|
||
if node.ClusterId <= 0 {
|
||
shouldSkip = true
|
||
return
|
||
}
|
||
addresses, addressesErr := SharedNodeIPAddressDAO.FindAllBackupNodeAccessAndUpIPAddresses(tx, int64(node.ClusterId), 0, nodeconfigs.NodeRoleNode)
|
||
if addressesErr != nil {
|
||
return false, false, nil, addressesErr
|
||
}
|
||
for _, address := range addresses {
|
||
var ip = address.DNSIP()
|
||
if !lists.ContainsString(ipAddressStrings, ip) {
|
||
ipAddressStrings = append(ipAddressStrings, ip)
|
||
}
|
||
}
|
||
if len(ipAddressStrings) > 0 {
|
||
shouldOverwrite = true
|
||
}
|
||
return
|
||
case nodeconfigs.NodeActionCodeSwitchToBackupNodesInGroup:
|
||
if node.GroupId <= 0 {
|
||
shouldSkip = true
|
||
return
|
||
}
|
||
addresses, addressesErr := SharedNodeIPAddressDAO.FindAllBackupNodeAccessAndUpIPAddresses(tx, 0, int64(node.GroupId), nodeconfigs.NodeRoleNode)
|
||
if addressesErr != nil {
|
||
return false, false, nil, addressesErr
|
||
}
|
||
for _, address := range addresses {
|
||
var ip = address.DNSIP()
|
||
if !lists.ContainsString(ipAddressStrings, ip) {
|
||
ipAddressStrings = append(ipAddressStrings, ip)
|
||
}
|
||
}
|
||
if len(ipAddressStrings) > 0 {
|
||
shouldOverwrite = true
|
||
}
|
||
return
|
||
case nodeconfigs.NodeActionCodeSwitchToBackupIP:
|
||
ipAddressStrings = node.DecodeBackupIPs()
|
||
shouldOverwrite = true
|
||
return
|
||
case nodeconfigs.NodeActionCodeEnableBackupNodesInCluster:
|
||
if node.ClusterId <= 0 {
|
||
shouldSkip = true
|
||
return
|
||
}
|
||
addresses, addressesErr := SharedNodeIPAddressDAO.FindAllBackupNodeAccessAndUpIPAddresses(tx, int64(node.ClusterId), 0, nodeconfigs.NodeRoleNode)
|
||
if addressesErr != nil {
|
||
return false, false, nil, addressesErr
|
||
}
|
||
for _, address := range addresses {
|
||
var ip = address.DNSIP()
|
||
if !lists.ContainsString(ipAddressStrings, ip) {
|
||
ipAddressStrings = append(ipAddressStrings, ip)
|
||
}
|
||
}
|
||
return
|
||
case nodeconfigs.NodeActionCodeEnableBackupNodesInGroup:
|
||
if node.GroupId <= 0 {
|
||
// 不能跳过
|
||
return
|
||
}
|
||
addresses, addressesErr := SharedNodeIPAddressDAO.FindAllBackupNodeAccessAndUpIPAddresses(tx, 0, int64(node.GroupId), nodeconfigs.NodeRoleNode)
|
||
if addressesErr != nil {
|
||
return false, false, nil, addressesErr
|
||
}
|
||
for _, address := range addresses {
|
||
var ip = address.DNSIP()
|
||
if !lists.ContainsString(ipAddressStrings, ip) {
|
||
ipAddressStrings = append(ipAddressStrings, ip)
|
||
}
|
||
}
|
||
return
|
||
case nodeconfigs.NodeActionCodeEnableBackupIP:
|
||
ipAddressStrings = node.DecodeBackupIPs()
|
||
return
|
||
case nodeconfigs.NodeActionCodeWebHook:
|
||
// do nothing
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
return
|
||
}
|