117 lines
5.1 KiB
Go
117 lines
5.1 KiB
Go
package service
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/constant"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/QuantumNous/new-api/setting/operation_setting"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPoolAffinityManualRoutingSwitch(t *testing.T) {
|
|
for _, change := range []string{"priority", "model_rule"} {
|
|
t.Run(change, func(t *testing.T) {
|
|
db := setupChannelSelectAutoGroupsTest(t)
|
|
const modelName = "manual-switch-model"
|
|
createChannelSelectAutoGroupsChannel(t, db, 6201, "default", modelName)
|
|
createChannelSelectAutoGroupsChannel(t, db, 6202, "vip", modelName)
|
|
model.InitChannelCache()
|
|
ResetGroupAvailability()
|
|
t.Cleanup(ResetGroupAvailability)
|
|
previousRedis := common.RedisEnabled
|
|
common.RedisEnabled = false
|
|
t.Cleanup(func() { common.RedisEnabled = previousRedis })
|
|
require.NoError(t, poolBindings().Purge())
|
|
t.Cleanup(func() { _ = poolBindings().Purge() })
|
|
config := operation_setting.GetChannelAffinitySetting()
|
|
previousConfig := *config
|
|
*config = operation_setting.ChannelAffinitySetting{Enabled: true, DefaultTTLSeconds: 3600, SessionMode: "strict", Rules: []operation_setting.ChannelAffinityRule{{Name: "switch-test", ModelRegex: []string{".*"}, KeySources: []operation_setting.ChannelAffinityKeySource{{Type: "request_header", Key: "Session-Id"}}, SessionMode: "strict"}}}
|
|
t.Cleanup(func() { *config = previousConfig })
|
|
previousLogDB := model.LOG_DB
|
|
model.LOG_DB = db
|
|
t.Cleanup(func() { model.LOG_DB = previousLogDB })
|
|
require.NoError(t, db.AutoMigrate(&model.Log{}))
|
|
ctx := func(switched bool) *gin.Context {
|
|
c := setupGroupPoolContext(t)
|
|
c.Request = httptest.NewRequest("POST", "/v1/responses", nil)
|
|
c.Request.Header.Set("Session-Id", "same-session")
|
|
c.Set("id", 100)
|
|
c.Set("token_id", 100)
|
|
c.Set("original_model", modelName)
|
|
policy := &model.TokenRoutingPolicy{Version: 1, Mode: "custom", Groups: []model.TokenGroupEntry{{Group: "default", Priority: 0}, {Group: "vip", Priority: 1}}}
|
|
if switched {
|
|
if change == "priority" {
|
|
policy.Groups[0].Priority = 1
|
|
policy.Groups[1].Priority = 0
|
|
} else {
|
|
policy.ModelRules = []model.ModelRoutingRule{{Model: modelName, Groups: []model.TokenGroupEntry{{Group: "vip", Priority: 0}}}}
|
|
}
|
|
}
|
|
c.Set(model.TokenRoutingContextKey, policy)
|
|
return c
|
|
}
|
|
old := ctx(false)
|
|
ch, _, selectErr := SelectChannelForRequest(old, modelName, &RetryParam{Ctx: old, TokenGroup: "default", ModelName: modelName, Retry: common.GetPointer(0)})
|
|
require.Nil(t, selectErr)
|
|
require.NotNil(t, ch)
|
|
assert.Equal(t, 6201, ch.Id)
|
|
old.Set("channel_id", ch.Id)
|
|
common.SetContextKey(old, constant.ContextKeyAutoGroup, "default")
|
|
RequestPolicy(old).Successful = true
|
|
RecordPoolAffinity(old)
|
|
unchanged, _ := PreferredPoolChannel(ctx(false), modelName)
|
|
require.NotNil(t, unchanged)
|
|
assert.Equal(t, 6201, unchanged.Id)
|
|
|
|
details := model.NewLogOther()
|
|
appendRequestDetails(old, nil, details)
|
|
body, err := common.Marshal(details.Snapshot())
|
|
require.NoError(t, err)
|
|
log := model.Log{UserId: 100, TokenId: 100, ModelName: modelName, CorrelationKey: old.GetString("log_correlation_key"), Type: model.LogTypeConsume, CreatedAt: time.Now().Unix(), ChannelId: 6201, Group: "default", Other: string(body)}
|
|
require.NoError(t, db.Create(&log).Error)
|
|
|
|
// The old group is still eligible: only its priority/preference changed.
|
|
switched := ctx(true)
|
|
selected, group, selectErr := SelectChannelForRequest(switched, modelName, &RetryParam{Ctx: switched, TokenGroup: "default", ModelName: modelName, Retry: common.GetPointer(0)})
|
|
require.Nil(t, selectErr)
|
|
require.NotNil(t, selected)
|
|
assert.Equal(t, 6202, selected.Id)
|
|
assert.Equal(t, "vip", group)
|
|
switched.Set("channel_id", selected.Id)
|
|
common.SetContextKey(switched, constant.ContextKeyAutoGroup, group)
|
|
RequestPolicy(switched).Successful = true
|
|
RecordPoolAffinity(switched)
|
|
unchanged, _ = PreferredPoolChannel(ctx(true), modelName)
|
|
require.NotNil(t, unchanged)
|
|
assert.Equal(t, 6202, unchanged.Id)
|
|
|
|
// A request started before the edit may finish after the new one.
|
|
RecordPoolAffinity(old)
|
|
stale, _ := PreferredPoolChannel(ctx(true), modelName)
|
|
assert.Nil(t, stale)
|
|
|
|
// Cache expiry/restart must not resurrect the old successful log.
|
|
require.NoError(t, poolBindings().Purge())
|
|
stale, _ = PreferredPoolChannel(ctx(true), modelName)
|
|
assert.Nil(t, stale)
|
|
|
|
// A matching historical policy remains recoverable after cache expiry.
|
|
require.NoError(t, poolBindings().Purge())
|
|
recovered, _ := PreferredPoolChannel(ctx(false), modelName)
|
|
require.NotNil(t, recovered)
|
|
assert.Equal(t, 6201, recovered.Id)
|
|
|
|
// Legacy bindings carry no policy and must be selected afresh once.
|
|
require.NoError(t, poolBindings().SetWithTTL(old.GetString("pool_affinity_key"), poolBinding{Channel: 6201, Group: "default"}, time.Hour))
|
|
legacy, _ := PreferredPoolChannel(ctx(false), modelName)
|
|
assert.Nil(t, legacy)
|
|
})
|
|
}
|
|
}
|