feat: add passive channel monitoring mode (#5592)

* feat: add passive channel monitoring mode

* fix: clarify passive monitor mode copy
This commit is contained in:
Herb Brewer
2026-06-22 18:07:44 +08:00
committed by GitHub
parent e5694748c7
commit efd6c445ac
14 changed files with 200 additions and 15 deletions
+48 -14
View File
@@ -893,12 +893,7 @@ func TestChannel(c *gin.Context) {
var testAllChannelsLock sync.Mutex
var testAllChannelsRunning bool = false
func testAllChannels(notify bool) error {
testUserID, err := resolveChannelTestUserID(nil)
if err != nil {
return err
}
func testChannels(channels []*model.Channel, testUserID int, notify bool, allowDisable bool) error {
testAllChannelsLock.Lock()
if testAllChannelsRunning {
testAllChannelsLock.Unlock()
@@ -906,10 +901,6 @@ func testAllChannels(notify bool) error {
}
testAllChannelsRunning = true
testAllChannelsLock.Unlock()
channels, getChannelErr := model.GetAllChannels(0, 0, true, false)
if getChannelErr != nil {
return getChannelErr
}
var disableThreshold = int64(common.ChannelDisableThreshold * 1000)
if disableThreshold == 0 {
disableThreshold = 10000000 // a impossible value
@@ -949,12 +940,12 @@ func testAllChannels(notify bool) error {
}
// disable channel
if isChannelEnabled && shouldBanChannel && channel.GetAutoBan() {
if allowDisable && isChannelEnabled && shouldBanChannel && channel.GetAutoBan() {
processChannelError(result.context, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(result.context, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError)
}
// enable channel
if !isChannelEnabled && service.ShouldEnableChannel(newAPIError, channel.Status) {
if result.localErr == nil && !isChannelEnabled && service.ShouldEnableChannel(newAPIError, channel.Status) {
service.EnableChannel(channel.Id, common.GetContextKeyString(result.context, constant.ContextKeyChannelKey), channel.Name)
}
@@ -969,6 +960,44 @@ func testAllChannels(notify bool) error {
return nil
}
func selectChannelsForAutomaticTest(channels []*model.Channel, mode string) []*model.Channel {
selected := make([]*model.Channel, 0, len(channels))
for _, channel := range channels {
if channel.Status == common.ChannelStatusManuallyDisabled {
continue
}
if mode == operation_setting.ChannelTestModePassiveRecovery && channel.Status != common.ChannelStatusAutoDisabled {
continue
}
selected = append(selected, channel)
}
return selected
}
func testAllChannels(notify bool) error {
testUserID, err := resolveChannelTestUserID(nil)
if err != nil {
return err
}
channels, getChannelErr := model.GetAllChannels(0, 0, true, false)
if getChannelErr != nil {
return getChannelErr
}
return testChannels(selectChannelsForAutomaticTest(channels, operation_setting.ChannelTestModeScheduledAll), testUserID, notify, true)
}
func testAutoDisabledChannels(notify bool) error {
testUserID, err := resolveChannelTestUserID(nil)
if err != nil {
return err
}
channels, getChannelErr := model.GetAllChannels(0, 0, true, false)
if getChannelErr != nil {
return getChannelErr
}
return testChannels(selectChannelsForAutomaticTest(channels, operation_setting.ChannelTestModePassiveRecovery), testUserID, notify, false)
}
func TestAllChannels(c *gin.Context) {
err := testAllChannels(true)
if err != nil {
@@ -998,8 +1027,13 @@ func AutomaticallyTestChannels() {
frequency := operation_setting.GetMonitorSetting().AutoTestChannelMinutes
time.Sleep(time.Duration(int(math.Round(frequency))) * time.Minute)
common.SysLog(fmt.Sprintf("automatically test channels with interval %f minutes", frequency))
common.SysLog("automatically testing all channels")
_ = testAllChannels(false)
if operation_setting.GetMonitorSetting().ChannelTestMode == operation_setting.ChannelTestModePassiveRecovery {
common.SysLog("automatically testing auto-disabled channels")
_ = testAutoDisabledChannels(false)
} else {
common.SysLog("automatically testing all channels")
_ = testAllChannels(false)
}
common.SysLog("automatically channel test finished")
if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled {
break
+29
View File
@@ -6,8 +6,10 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/pkg/billingexpr"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/QuantumNous/new-api/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
@@ -80,3 +82,30 @@ func TestResolveChannelTestUserIDUsesRequestUser(t *testing.T) {
require.NoError(t, err)
require.Equal(t, 2, userID)
}
func TestSelectChannelsForAutomaticTestPassiveRecoveryOnlyUsesAutoDisabled(t *testing.T) {
channels := []*model.Channel{
{Id: 1, Status: common.ChannelStatusEnabled},
{Id: 2, Status: common.ChannelStatusAutoDisabled},
{Id: 3, Status: common.ChannelStatusManuallyDisabled},
}
selected := selectChannelsForAutomaticTest(channels, operation_setting.ChannelTestModePassiveRecovery)
require.Len(t, selected, 1)
require.Equal(t, 2, selected[0].Id)
}
func TestSelectChannelsForAutomaticTestScheduledSkipsManualDisabled(t *testing.T) {
channels := []*model.Channel{
{Id: 1, Status: common.ChannelStatusEnabled},
{Id: 2, Status: common.ChannelStatusAutoDisabled},
{Id: 3, Status: common.ChannelStatusManuallyDisabled},
}
selected := selectChannelsForAutomaticTest(channels, operation_setting.ChannelTestModeScheduledAll)
require.Len(t, selected, 2)
require.Equal(t, 1, selected[0].Id)
require.Equal(t, 2, selected[1].Id)
}