498b235461
Build and test / Build and test AMD64 Ubuntu 22.04 (push) Failing after 0s
Publish Builder / amazonlinux2023 (push) Failing after 1s
Build and test / UT for Go (push) Has been skipped
Publish KRTE Images / KRTE (push) Failing after 1s
Build and test / Integration Test (push) Has been skipped
Build and test / Upload Code Coverage (push) Has been skipped
Publish Builder / rockylinux9 (push) Failing after 1s
Publish Builder / ubuntu22.04 (push) Failing after 0s
Publish Builder / ubuntu24.04 (push) Failing after 0s
Publish Gpu Builder / publish-gpu-builder (push) Failing after 1s
Publish Test Images / PyTest (push) Failing after 0s
Build and test / UT for Cpp (push) Has been cancelled
3267 lines
98 KiB
Go
3267 lines
98 KiB
Go
// Licensed to the LF AI & Data foundation under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package task
|
|
|
|
import (
|
|
"context"
|
|
"math/rand"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/samber/lo"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
|
|
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
|
|
"github.com/milvus-io/milvus-proto/go-api/v3/schemapb"
|
|
"github.com/milvus-io/milvus/internal/coordinator/snmanager"
|
|
"github.com/milvus-io/milvus/internal/json"
|
|
etcdkv "github.com/milvus-io/milvus/internal/kv/etcd"
|
|
"github.com/milvus-io/milvus/internal/metastore"
|
|
"github.com/milvus-io/milvus/internal/metastore/kv/querycoord"
|
|
"github.com/milvus-io/milvus/internal/querycoordv2/meta"
|
|
. "github.com/milvus-io/milvus/internal/querycoordv2/params"
|
|
"github.com/milvus-io/milvus/internal/querycoordv2/session"
|
|
"github.com/milvus-io/milvus/internal/querycoordv2/utils"
|
|
kvfactory "github.com/milvus-io/milvus/internal/util/dependency/kv"
|
|
"github.com/milvus-io/milvus/pkg/v3/common"
|
|
"github.com/milvus-io/milvus/pkg/v3/kv"
|
|
"github.com/milvus-io/milvus/pkg/v3/proto/datapb"
|
|
"github.com/milvus-io/milvus/pkg/v3/proto/indexpb"
|
|
"github.com/milvus-io/milvus/pkg/v3/proto/querypb"
|
|
"github.com/milvus-io/milvus/pkg/v3/proto/rootcoordpb"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/merr"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/metricsinfo"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
|
|
"github.com/milvus-io/milvus/pkg/v3/util/typeutil"
|
|
)
|
|
|
|
type distribution struct {
|
|
NodeID int64
|
|
channels typeutil.Set[string]
|
|
segments typeutil.Set[int64]
|
|
}
|
|
|
|
type TaskSuite struct {
|
|
suite.Suite
|
|
|
|
// Data
|
|
collection int64
|
|
replica *meta.Replica
|
|
subChannels []string
|
|
unsubChannels []string
|
|
moveChannels []string
|
|
growingSegments map[string]int64
|
|
loadSegments []int64
|
|
releaseSegments []int64
|
|
moveSegments []int64
|
|
distributions map[int64]*distribution
|
|
|
|
// Dependencies
|
|
kv kv.MetaKv
|
|
store metastore.QueryCoordCatalog
|
|
meta *meta.Meta
|
|
dist *meta.DistributionManager
|
|
target *meta.TargetManager
|
|
broker *meta.MockBroker
|
|
nodeMgr *session.NodeManager
|
|
cluster *session.MockCluster
|
|
|
|
// Test object
|
|
scheduler *taskScheduler
|
|
ctx context.Context
|
|
}
|
|
|
|
func (suite *TaskSuite) SetupSuite() {
|
|
paramtable.Init()
|
|
suite.collection = 1000
|
|
suite.replica = meta.NewReplica(&querypb.Replica{
|
|
CollectionID: suite.collection,
|
|
ID: 10,
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
Nodes: []int64{1, 2, 3},
|
|
}, typeutil.NewUniqueSet(1, 2, 3))
|
|
|
|
suite.subChannels = []string{
|
|
"sub-0",
|
|
"sub-1",
|
|
}
|
|
suite.unsubChannels = []string{
|
|
"unsub-2",
|
|
"unsub-3",
|
|
}
|
|
suite.moveChannels = []string{
|
|
"move-4",
|
|
"move-5",
|
|
}
|
|
suite.growingSegments = map[string]int64{
|
|
"sub-0": 10,
|
|
"sub-1": 11,
|
|
}
|
|
suite.loadSegments = []int64{1, 2}
|
|
suite.releaseSegments = []int64{3, 4}
|
|
suite.moveSegments = []int64{5, 6}
|
|
suite.distributions = map[int64]*distribution{
|
|
1: {
|
|
NodeID: 1,
|
|
channels: typeutil.NewSet("unsub-2", "move-4"),
|
|
segments: typeutil.NewSet[int64](3, 5),
|
|
},
|
|
2: {
|
|
NodeID: 2,
|
|
channels: typeutil.NewSet("unsub-3", "move-5"),
|
|
segments: typeutil.NewSet[int64](4, 6),
|
|
},
|
|
3: {
|
|
NodeID: 3,
|
|
channels: typeutil.NewSet[string](),
|
|
segments: typeutil.NewSet[int64](),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TearDownSuite() {
|
|
paramtable.Get().Reset(paramtable.Get().EtcdCfg.Endpoints.Key)
|
|
}
|
|
|
|
func (suite *TaskSuite) SetupTest() {
|
|
snmanager.ResetDoNothingStreamingNodeManager(suite.T())
|
|
|
|
config := GenerateEtcdConfig()
|
|
suite.ctx = context.Background()
|
|
cli, _ := kvfactory.GetEtcdAndPath()
|
|
|
|
suite.kv = etcdkv.NewEtcdKV(cli, config.MetaRootPath.GetValue())
|
|
suite.store = querycoord.NewCatalog(suite.kv)
|
|
suite.meta = meta.NewMeta(RandomIncrementIDAllocator(), suite.store, session.NewNodeManager())
|
|
suite.meta.Put(suite.ctx, suite.replica)
|
|
suite.nodeMgr = session.NewNodeManager()
|
|
suite.dist = meta.NewDistributionManager(suite.nodeMgr)
|
|
suite.broker = meta.NewMockBroker(suite.T())
|
|
suite.target = meta.NewTargetManager(suite.broker, suite.meta)
|
|
suite.cluster = session.NewMockCluster(suite.T())
|
|
|
|
suite.scheduler = suite.newScheduler()
|
|
suite.scheduler.Start()
|
|
suite.scheduler.AddExecutor(1)
|
|
suite.scheduler.AddExecutor(2)
|
|
suite.scheduler.AddExecutor(3)
|
|
meta.GlobalFailedLoadCache = meta.NewFailedLoadCache()
|
|
}
|
|
|
|
func (suite *TaskSuite) BeforeTest(suiteName, testName string) {
|
|
for node := range suite.distributions {
|
|
suite.nodeMgr.Add(session.NewNodeInfo(session.ImmutableNodeInfo{
|
|
NodeID: node,
|
|
Address: "localhost",
|
|
Hostname: "localhost",
|
|
}))
|
|
}
|
|
|
|
switch testName {
|
|
case "TestSubscribeChannelTask",
|
|
"TestUnsubscribeChannelTask",
|
|
"TestLoadSegmentTask",
|
|
"TestLoadSegmentTaskNotIndex",
|
|
"TestSegmentTaskWaitsDistAfterLoadRPC",
|
|
"TestLoadSegmentTaskFailed",
|
|
"TestTaskCanceled",
|
|
"TestMoveSegmentTask",
|
|
"TestSubmitDuplicateLoadSegmentTask",
|
|
"TestSubmitDuplicateSubscribeChannelTask",
|
|
"TestLeaderTaskSet",
|
|
"TestLeaderTaskRemove",
|
|
"TestNoExecutor",
|
|
"TestTaskStaleByRONode",
|
|
"TestTaskStaleBySegmentInDist",
|
|
"TestLeaderTaskStaleByRONode":
|
|
suite.meta.PutCollection(suite.ctx, &meta.Collection{
|
|
CollectionLoadInfo: &querypb.CollectionLoadInfo{
|
|
CollectionID: suite.collection,
|
|
ReplicaNumber: 1,
|
|
Status: querypb.LoadStatus_Loading,
|
|
},
|
|
})
|
|
suite.meta.PutPartition(suite.ctx, &meta.Partition{
|
|
PartitionLoadInfo: &querypb.PartitionLoadInfo{
|
|
CollectionID: suite.collection,
|
|
PartitionID: 1,
|
|
},
|
|
})
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSubscribeChannelTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partitions := []int64{100, 101}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).
|
|
RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestSubscribeChannelTask",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().DescribeDatabase(mock.Anything, mock.Anything).Return(&rootcoordpb.DescribeDatabaseResponse{}, nil)
|
|
for channel, segment := range suite.growingSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).
|
|
Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partitions[0],
|
|
InsertChannel: channel,
|
|
},
|
|
}, nil)
|
|
}
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
FieldID: 100,
|
|
IndexParams: []*commonpb.KeyValuePair{
|
|
{
|
|
Key: common.MetricTypeKey,
|
|
Value: "L2",
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
suite.cluster.EXPECT().WatchDmChannels(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test subscribe channel task
|
|
tasks := []Task{}
|
|
dmChannels := make([]*datapb.VchannelInfo, 0)
|
|
for _, channel := range suite.subChannels {
|
|
dmChannels = append(dmChannels, &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel,
|
|
UnflushedSegmentIds: []int64{suite.growingSegments[channel]},
|
|
})
|
|
task, err := NewChannelTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
NewChannelAction(targetNode, ActionTypeGrow, channel),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return(dmChannels, nil, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
suite.AssertTaskNum(0, len(suite.subChannels), len(suite.subChannels), 0)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(len(suite.subChannels), 0, len(suite.subChannels), 0)
|
|
|
|
// Process tasks done
|
|
// Dist contains channels
|
|
channels := []*meta.DmChannel{}
|
|
for _, channel := range suite.subChannels {
|
|
channels = append(channels, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel,
|
|
},
|
|
})
|
|
}
|
|
suite.dist.ChannelDistManager.Update(targetNode, channels...)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
Wait(ctx, timeout, tasks...)
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSubmitDuplicateSubscribeChannelTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
|
|
tasks := []Task{}
|
|
for _, channel := range suite.subChannels {
|
|
task, err := NewChannelTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
NewChannelAction(targetNode, ActionTypeGrow, channel),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
}
|
|
|
|
channels := []*meta.DmChannel{}
|
|
for _, channel := range suite.subChannels {
|
|
channels = append(channels, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel,
|
|
},
|
|
})
|
|
}
|
|
suite.dist.ChannelDistManager.Update(targetNode, channels...)
|
|
|
|
for _, task := range tasks {
|
|
err := suite.scheduler.Add(task)
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.Error(err)
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestUnsubscribeChannelTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(1)
|
|
|
|
// Expect
|
|
suite.cluster.EXPECT().UnsubDmChannel(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test unsubscribe channel task
|
|
tasks := []Task{}
|
|
dmChannels := make([]*datapb.VchannelInfo, 0)
|
|
for _, channel := range suite.unsubChannels {
|
|
dmChannels = append(dmChannels, &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel,
|
|
})
|
|
task, err := NewChannelTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
meta.NilReplica,
|
|
NewChannelAction(targetNode, ActionTypeReduce, channel),
|
|
)
|
|
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return(dmChannels, nil, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
|
|
// Only first channel exists
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: suite.unsubChannels[0],
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: suite.unsubChannels[0],
|
|
},
|
|
})
|
|
suite.AssertTaskNum(0, len(suite.unsubChannels), len(suite.unsubChannels), 0)
|
|
|
|
// ProcessTasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(1, 0, 1, 0)
|
|
|
|
// Update dist
|
|
suite.dist.ChannelDistManager.Update(targetNode)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLoadSegmentTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestLoadSegmentTask",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
},
|
|
}, nil)
|
|
for _, segment := range suite.loadSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
|
|
}
|
|
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test load segment task
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: channel,
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
},
|
|
})
|
|
tasks := []Task{}
|
|
segments := make([]*datapb.SegmentInfo, 0)
|
|
for _, segment := range suite.loadSegments {
|
|
segments = append(segments, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
InsertChannel: channel.ChannelName,
|
|
PartitionID: 1,
|
|
})
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
segmentsNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Process tasks done
|
|
// Dist contains channels
|
|
view := &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Segments: map[int64]*querypb.SegmentDist{},
|
|
Channel: channel.ChannelName,
|
|
}
|
|
for _, segment := range suite.loadSegments {
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
|
|
}
|
|
distSegments := lo.Map(segments, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
|
|
return meta.SegmentFromInfo(info)
|
|
})
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: view,
|
|
})
|
|
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLoadSegmentTaskNotIndex() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestLoadSegmentTaskNotIndex",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
},
|
|
}, nil)
|
|
for _, segment := range suite.loadSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, merr.WrapErrIndexNotFoundForSegments([]int64{segment}))
|
|
}
|
|
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test load segment task
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
tasks := []Task{}
|
|
segments := make([]*datapb.SegmentInfo, 0)
|
|
for _, segment := range suite.loadSegments {
|
|
segments = append(segments, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
InsertChannel: channel.ChannelName,
|
|
PartitionID: 1,
|
|
})
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
segmentsNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Process tasks done
|
|
// Dist contains channels
|
|
view := &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Segments: map[int64]*querypb.SegmentDist{},
|
|
Channel: channel.ChannelName,
|
|
}
|
|
for _, segment := range suite.loadSegments {
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
|
|
}
|
|
distSegments := lo.Map(segments, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
|
|
return meta.SegmentFromInfo(info)
|
|
})
|
|
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLoadSegmentTaskFailed() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestLoadSegmentTaskNotIndex",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
for _, segment := range suite.loadSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, errors.New("index not ready"))
|
|
}
|
|
|
|
// Test load segment task
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
tasks := []Task{}
|
|
segments := make([]*datapb.SegmentInfo, 0)
|
|
for _, segment := range suite.loadSegments {
|
|
segments = append(segments, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
PartitionID: 1,
|
|
InsertChannel: channel.ChannelName,
|
|
})
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
segmentsNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Process tasks done
|
|
// Dist contains channels
|
|
time.Sleep(timeout)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusFailed, task.Status())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestReleaseSegmentTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test load segment task
|
|
view := &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Segments: make(map[int64]*querypb.SegmentDist),
|
|
}
|
|
segments := make([]*meta.Segment, 0)
|
|
tasks := []Task{}
|
|
for _, segment := range suite.releaseSegments {
|
|
segments = append(segments, &meta.Segment{
|
|
SegmentInfo: &datapb.SegmentInfo{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
})
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.dist.SegmentDistManager.Update(targetNode, segments...)
|
|
|
|
segmentsNum := len(suite.releaseSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Process tasks done
|
|
suite.dist.SegmentDistManager.Update(targetNode)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestReleaseGrowingSegmentTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
|
|
// Expect
|
|
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
tasks := []Task{}
|
|
for _, segment := range suite.releaseSegments {
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(targetNode, ActionTypeReduce, "", segment, querypb.DataScope_Streaming, 0),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
|
|
segmentsNum := len(suite.releaseSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks and Release done
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Tasks removed
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskWaitsDistAfterLoadRPC() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
segmentID := suite.loadSegments[0]
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestSegmentTaskWaitsDistAfterLoadRPC",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{{CollectionID: suite.collection}}, nil)
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segmentID).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segmentID,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segmentID).Return(nil, nil)
|
|
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil).Once()
|
|
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: channel,
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
},
|
|
})
|
|
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segmentID),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, []*datapb.SegmentInfo{
|
|
{
|
|
ID: segmentID,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
|
|
suite.NoError(suite.scheduler.Add(task))
|
|
suite.AssertTaskNum(0, 1, 0, 1)
|
|
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(1, 0, 0, 1)
|
|
suite.Equal(TaskStatusStarted, task.Status())
|
|
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(1, 0, 0, 1)
|
|
suite.Equal(TaskStatusStarted, task.Status())
|
|
|
|
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, partition, segmentID, targetNode, 1, channel.ChannelName))
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskWaitsDistAfterReleaseRPC() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
segmentID := suite.releaseSegments[0]
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil).Once()
|
|
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), segmentID),
|
|
)
|
|
suite.NoError(err)
|
|
suite.NoError(suite.scheduler.Add(task))
|
|
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, partition, segmentID, targetNode, 1, channel.ChannelName))
|
|
suite.AssertTaskNum(0, 1, 0, 1)
|
|
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(1, 0, 0, 1)
|
|
suite.Equal(TaskStatusStarted, task.Status())
|
|
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(1, 0, 0, 1)
|
|
suite.Equal(TaskStatusStarted, task.Status())
|
|
|
|
suite.dist.SegmentDistManager.Update(targetNode)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskChecksTimeoutWhileWaitingDist() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
|
defer cancel()
|
|
|
|
action := NewSegmentAction(3, ActionTypeGrow, "test-channel", suite.loadSegments[0])
|
|
action.rpcReturned.Store(true)
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
action,
|
|
)
|
|
suite.NoError(err)
|
|
time.Sleep(time.Millisecond)
|
|
|
|
shouldProcess := suite.scheduler.preProcess(task)
|
|
suite.False(shouldProcess)
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.ErrorIs(task.Err(), context.DeadlineExceeded)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestStreamingReduceWaitsForGrowingSegmentDist() {
|
|
nodeID := int64(3)
|
|
channelName := "test-channel"
|
|
segmentID := suite.growingSegments["sub-0"]
|
|
suite.dist.ChannelDistManager.Update(nodeID, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channelName,
|
|
},
|
|
Node: nodeID,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: nodeID,
|
|
CollectionID: suite.collection,
|
|
Channel: channelName,
|
|
GrowingSegments: map[int64]*meta.Segment{
|
|
segmentID: utils.CreateTestSegment(suite.collection, 1, segmentID, nodeID, 1, channelName),
|
|
},
|
|
},
|
|
})
|
|
|
|
action := NewSegmentActionWithScope(nodeID, ActionTypeReduce, channelName, segmentID, querypb.DataScope_Streaming, 0)
|
|
action.rpcReturned.Store(true)
|
|
suite.False(action.IsFinished(suite.dist))
|
|
|
|
suite.dist.ChannelDistManager.Update(nodeID)
|
|
suite.True(action.IsFinished(suite.dist))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestMoveSegmentTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
leader := int64(1)
|
|
sourceNode := int64(2)
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestMoveSegmentTask",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
},
|
|
}, nil)
|
|
for _, segment := range suite.moveSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
|
|
}
|
|
suite.cluster.EXPECT().LoadSegments(mock.Anything, leader, mock.Anything).Return(merr.Success(), nil)
|
|
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, leader, mock.Anything).Return(merr.Success(), nil)
|
|
vchannel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
}
|
|
suite.dist.ChannelDistManager.Update(leader, &meta.DmChannel{
|
|
VchannelInfo: vchannel,
|
|
Node: leader,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: leader,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
},
|
|
})
|
|
view := &meta.LeaderView{
|
|
ID: leader,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Segments: make(map[int64]*querypb.SegmentDist),
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
}
|
|
tasks := []Task{}
|
|
segments := make([]*meta.Segment, 0)
|
|
segmentInfos := make([]*datapb.SegmentInfo, 0)
|
|
for _, segment := range suite.moveSegments {
|
|
segments = append(segments,
|
|
utils.CreateTestSegment(suite.collection, partition, segment, sourceNode, 1, channel.ChannelName))
|
|
segmentInfos = append(segmentInfos, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
PartitionID: 1,
|
|
InsertChannel: channel.ChannelName,
|
|
})
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: sourceNode, Version: 0}
|
|
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
|
|
NewSegmentAction(sourceNode, ActionTypeReduce, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{vchannel}, segmentInfos, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
suite.target.UpdateCollectionCurrentTarget(ctx, suite.collection)
|
|
suite.dist.SegmentDistManager.Update(sourceNode, segments...)
|
|
suite.dist.ChannelDistManager.Update(leader, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: leader,
|
|
View: view,
|
|
})
|
|
for _, task := range tasks {
|
|
err := suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
|
|
segmentsNum := len(suite.moveSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Process tasks, target node contains the segment
|
|
view = view.Clone()
|
|
for _, segment := range suite.moveSegments {
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
|
|
}
|
|
distSegments := lo.Map(segmentInfos, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
|
|
return meta.SegmentFromInfo(info)
|
|
})
|
|
|
|
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
|
|
// First action done, execute the second action
|
|
suite.dispatchAndWait(sourceNode)
|
|
suite.dist.SegmentDistManager.Update(sourceNode)
|
|
// Check second action
|
|
suite.dispatchAndWait(sourceNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestTaskCanceled() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestSubscribeChannelTask",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
},
|
|
}, nil)
|
|
for _, segment := range suite.loadSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
|
|
}
|
|
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test load segment task
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
tasks := []Task{}
|
|
segmentInfos := []*datapb.SegmentInfo{}
|
|
for _, segment := range suite.loadSegments {
|
|
segmentInfos = append(segmentInfos, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.GetChannelName(),
|
|
})
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
tasks = append(tasks, task)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
segmentsNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segmentInfos, nil)
|
|
suite.meta.PutPartition(ctx, utils.CreateTestPartition(suite.collection, partition))
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Cancel all tasks
|
|
for _, task := range tasks {
|
|
task.Cancel(errors.New("mock error"))
|
|
}
|
|
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.Error(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestChannelTaskReplace() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
|
|
for _, channel := range suite.subChannels {
|
|
task, err := NewChannelTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
NewChannelAction(targetNode, ActionTypeGrow, channel),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetPriority(TaskPriorityNormal)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
|
|
// Task with the same replica and segment,
|
|
// but without higher priority can't be added
|
|
for _, channel := range suite.subChannels {
|
|
task, err := NewChannelTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
NewChannelAction(targetNode, ActionTypeGrow, channel),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetPriority(TaskPriorityNormal)
|
|
err = suite.scheduler.Add(task)
|
|
suite.Error(err)
|
|
task.SetPriority(TaskPriorityLow)
|
|
err = suite.scheduler.Add(task)
|
|
suite.Error(err)
|
|
}
|
|
|
|
// Replace the task with one with higher priority
|
|
for _, channel := range suite.subChannels {
|
|
task, err := NewChannelTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
NewChannelAction(targetNode, ActionTypeGrow, channel),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetPriority(TaskPriorityHigh)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
channelNum := len(suite.subChannels)
|
|
suite.AssertTaskNum(0, channelNum, channelNum, 0)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLeaderTaskSet() {
|
|
ctx := context.Background()
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestLoadSegmentTask",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
},
|
|
}, nil)
|
|
for _, segment := range suite.loadSegments {
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segment).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segment).Return(nil, nil)
|
|
}
|
|
suite.cluster.EXPECT().SyncDistribution(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test load segment task
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: channel,
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
tasks := []Task{}
|
|
segments := make([]*datapb.SegmentInfo, 0)
|
|
for _, segment := range suite.loadSegments {
|
|
segments = append(segments, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
InsertChannel: channel.ChannelName,
|
|
PartitionID: 1,
|
|
})
|
|
task := NewLeaderSegmentTask(
|
|
ctx,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
targetNode,
|
|
NewLeaderAction(targetNode, targetNode, ActionTypeGrow, channel.GetChannelName(), segment, 0),
|
|
)
|
|
tasks = append(tasks, task)
|
|
err := suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
segmentsNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
view := &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.GetChannelName(),
|
|
Segments: map[int64]*querypb.SegmentDist{},
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
}
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.GetChannelName(),
|
|
},
|
|
Node: targetNode,
|
|
View: view,
|
|
})
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// Process tasks done
|
|
// Dist contains channels
|
|
view = &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.GetChannelName(),
|
|
Segments: map[int64]*querypb.SegmentDist{},
|
|
}
|
|
for _, segment := range suite.loadSegments {
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
|
|
}
|
|
distSegments := lo.Map(segments, func(info *datapb.SegmentInfo, _ int) *meta.Segment {
|
|
return meta.SegmentFromInfo(info)
|
|
})
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.GetChannelName(),
|
|
},
|
|
Node: targetNode,
|
|
View: view,
|
|
})
|
|
suite.dist.SegmentDistManager.Update(targetNode, distSegments...)
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestCreateTaskBehavior() {
|
|
chanelTask, err := NewChannelTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica)
|
|
suite.ErrorIs(err, merr.ErrParameterInvalid)
|
|
suite.Nil(chanelTask)
|
|
|
|
action := NewSegmentAction(0, 0, "", 0)
|
|
chanelTask, err = NewChannelTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, action)
|
|
suite.ErrorIs(err, merr.ErrParameterInvalid)
|
|
suite.Nil(chanelTask)
|
|
|
|
action1 := NewChannelAction(0, 0, "fake-channel1")
|
|
action2 := NewChannelAction(0, 0, "fake-channel2")
|
|
chanelTask, err = NewChannelTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, action1, action2)
|
|
suite.ErrorIs(err, merr.ErrParameterInvalid)
|
|
suite.Nil(chanelTask)
|
|
|
|
segmentTask, err := NewSegmentTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, commonpb.LoadPriority_LOW)
|
|
suite.ErrorIs(err, merr.ErrParameterInvalid)
|
|
suite.Nil(segmentTask)
|
|
|
|
channelAction := NewChannelAction(0, 0, "fake-channel1")
|
|
segmentTask, err = NewSegmentTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, commonpb.LoadPriority_LOW, channelAction)
|
|
suite.ErrorIs(err, merr.ErrParameterInvalid)
|
|
suite.Nil(segmentTask)
|
|
|
|
segmentAction1 := NewSegmentAction(0, 0, "", 0)
|
|
segmentAction2 := NewSegmentAction(0, 0, "", 1)
|
|
|
|
segmentTask, err = NewSegmentTask(context.TODO(), 5*time.Second, WrapIDSource(0), 0, meta.NilReplica, commonpb.LoadPriority_LOW, segmentAction1, segmentAction2)
|
|
suite.ErrorIs(err, merr.ErrParameterInvalid)
|
|
suite.Nil(segmentTask)
|
|
|
|
leaderAction := NewLeaderAction(1, 2, ActionTypeGrow, "fake-channel1", 100, 0)
|
|
leaderTask := NewLeaderSegmentTask(context.TODO(), WrapIDSource(0), 0, meta.NilReplica, 1, leaderAction)
|
|
suite.NotNil(leaderTask)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskReplace() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
|
|
for _, segment := range suite.loadSegments {
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, "", segment),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetPriority(TaskPriorityNormal)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
|
|
// Task with the same replica and segment,
|
|
// but without higher priority can't be added
|
|
for _, segment := range suite.loadSegments {
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, "", segment),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetPriority(TaskPriorityNormal)
|
|
err = suite.scheduler.Add(task)
|
|
suite.Error(err)
|
|
task.SetPriority(TaskPriorityLow)
|
|
err = suite.scheduler.Add(task)
|
|
suite.Error(err)
|
|
}
|
|
|
|
// Replace the task with one with higher priority
|
|
for _, segment := range suite.loadSegments {
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, "", segment),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetPriority(TaskPriorityHigh)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
segmentNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentNum, 0, segmentNum)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestNoExecutor() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(-1)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
suite.meta.Put(ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3, -1}))
|
|
|
|
// Test load segment task
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: channel,
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
segments := make([]*datapb.SegmentInfo, 0)
|
|
for _, segment := range suite.loadSegments {
|
|
segments = append(segments, &datapb.SegmentInfo{
|
|
ID: segment,
|
|
PartitionID: 1,
|
|
InsertChannel: channel.ChannelName,
|
|
})
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segment),
|
|
)
|
|
suite.NoError(err)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segments, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
segmentsNum := len(suite.loadSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestRemoveByNodeWaitsForSchedule() {
|
|
ctx := context.Background()
|
|
targetNode := int64(1)
|
|
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, "ch-0", 999),
|
|
)
|
|
suite.NoError(err)
|
|
suite.NoError(suite.scheduler.Add(task))
|
|
suite.AssertTaskNum(0, 1, 0, 1)
|
|
|
|
suite.scheduler.scheduleMu.Lock()
|
|
locked := true
|
|
defer func() {
|
|
if locked {
|
|
suite.scheduler.scheduleMu.Unlock()
|
|
}
|
|
}()
|
|
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
suite.scheduler.RemoveByNode(targetNode)
|
|
}()
|
|
|
|
select {
|
|
case <-done:
|
|
suite.FailNow("RemoveByNode should wait for scheduleMu")
|
|
case <-time.After(50 * time.Millisecond):
|
|
}
|
|
suite.AssertTaskNum(0, 1, 0, 1)
|
|
|
|
suite.scheduler.scheduleMu.Unlock()
|
|
locked = false
|
|
|
|
select {
|
|
case <-done:
|
|
case <-time.After(time.Second):
|
|
suite.FailNow("RemoveByNode blocked after scheduleMu was released")
|
|
}
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
}
|
|
|
|
func (suite *TaskSuite) AssertTaskNum(process, wait, channel, segment int) {
|
|
scheduler := suite.scheduler
|
|
|
|
suite.Equal(process, scheduler.processQueue.Len())
|
|
suite.Equal(wait, scheduler.waitQueue.Len())
|
|
suite.Equal(scheduler.segmentTasks.Len(), segment)
|
|
suite.Equal(scheduler.channelTasks.Len(), channel)
|
|
suite.Equal(scheduler.tasks.Len(), process+wait)
|
|
suite.Equal(scheduler.tasks.Len(), segment+channel)
|
|
}
|
|
|
|
func (suite *TaskSuite) dispatchAndWait(node int64) {
|
|
timeout := 10 * time.Second
|
|
suite.scheduler.Dispatch(node)
|
|
var keys []any
|
|
count := 0
|
|
for start := time.Now(); time.Since(start) < timeout; {
|
|
count = 0
|
|
keys = make([]any, 0)
|
|
|
|
suite.scheduler.executors.Range(func(_ int64, executor *Executor) bool {
|
|
executor.executingTasks.Range(func(taskIndex string) bool {
|
|
keys = append(keys, taskIndex)
|
|
count++
|
|
return true
|
|
})
|
|
return true
|
|
})
|
|
|
|
if count == 0 {
|
|
return
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
suite.FailNow("executor hangs in executing tasks", "count=%d keys=%+v", count, keys)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLeaderTaskRemove() {
|
|
ctx := context.Background()
|
|
targetNode := int64(3)
|
|
partition := int64(100)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Expect
|
|
suite.cluster.EXPECT().SyncDistribution(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
// Test remove segment task
|
|
view := &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Segments: make(map[int64]*querypb.SegmentDist),
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
}
|
|
segments := make([]*meta.Segment, 0)
|
|
tasks := []Task{}
|
|
for _, segment := range suite.releaseSegments {
|
|
segments = append(segments, &meta.Segment{
|
|
SegmentInfo: &datapb.SegmentInfo{
|
|
ID: segment,
|
|
CollectionID: suite.collection,
|
|
PartitionID: partition,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
})
|
|
view.Segments[segment] = &querypb.SegmentDist{NodeID: targetNode, Version: 0}
|
|
task := NewLeaderSegmentTask(
|
|
ctx,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
targetNode,
|
|
NewLeaderAction(targetNode, targetNode, ActionTypeReduce, channel.GetChannelName(), segment, 0),
|
|
)
|
|
tasks = append(tasks, task)
|
|
err := suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
}
|
|
suite.dist.SegmentDistManager.Update(targetNode, segments...)
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: view,
|
|
})
|
|
|
|
segmentsNum := len(suite.releaseSegments)
|
|
suite.AssertTaskNum(0, segmentsNum, 0, segmentsNum)
|
|
|
|
// Process tasks
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(segmentsNum, 0, 0, segmentsNum)
|
|
|
|
// mock leader view which has removed all segments
|
|
view.Segments = make(map[int64]*querypb.SegmentDist)
|
|
// Process tasks done
|
|
suite.dispatchAndWait(targetNode)
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
|
|
for _, task := range tasks {
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
}
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLeaderTaskUsesLeaderExecutor() {
|
|
ctx := context.Background()
|
|
leaderID := int64(1)
|
|
workerID := int64(2)
|
|
segmentID := suite.releaseSegments[0]
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-leader-executor",
|
|
}
|
|
|
|
suite.scheduler.RemoveExecutor(workerID)
|
|
suite.cluster.EXPECT().SyncDistribution(mock.Anything, leaderID, mock.MatchedBy(func(req *querypb.SyncDistributionRequest) bool {
|
|
return req.GetCollectionID() == suite.collection &&
|
|
req.GetChannel() == channel.GetChannelName() &&
|
|
len(req.GetActions()) == 1 &&
|
|
req.GetActions()[0].GetType() == querypb.SyncType_Remove &&
|
|
req.GetActions()[0].GetSegmentID() == segmentID &&
|
|
req.GetActions()[0].GetNodeID() == workerID
|
|
})).Return(merr.Success(), nil).Once()
|
|
|
|
task := NewLeaderSegmentTask(
|
|
ctx,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
leaderID,
|
|
NewLeaderAction(leaderID, workerID, ActionTypeReduce, channel.GetChannelName(), segmentID, 0),
|
|
)
|
|
suite.NoError(suite.scheduler.Add(task))
|
|
|
|
suite.dispatchAndWait(leaderID)
|
|
|
|
suite.dispatchAndWait(leaderID)
|
|
suite.Equal(TaskStatusSucceeded, task.Status())
|
|
suite.NoError(task.Err())
|
|
suite.AssertTaskNum(0, 0, 0, 0)
|
|
}
|
|
|
|
func (suite *TaskSuite) newScheduler() *taskScheduler {
|
|
return NewScheduler(
|
|
context.Background(),
|
|
suite.meta,
|
|
suite.dist,
|
|
suite.target,
|
|
suite.broker,
|
|
suite.cluster,
|
|
suite.nodeMgr,
|
|
)
|
|
}
|
|
|
|
func createReplica(collection int64, nodes ...int64) *meta.Replica {
|
|
return meta.NewReplica(
|
|
&querypb.Replica{
|
|
ID: rand.Int63()/2 + 1,
|
|
CollectionID: collection,
|
|
Nodes: nodes,
|
|
},
|
|
typeutil.NewUniqueSet(nodes...),
|
|
)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestBalanceChannelTask() {
|
|
ctx := context.Background()
|
|
collectionID := suite.collection
|
|
partitionID := int64(1)
|
|
channel := "channel-1"
|
|
vchannel := &datapb.VchannelInfo{
|
|
CollectionID: collectionID,
|
|
ChannelName: channel,
|
|
}
|
|
|
|
segments := []*datapb.SegmentInfo{
|
|
{
|
|
ID: 1,
|
|
CollectionID: collectionID,
|
|
PartitionID: partitionID,
|
|
InsertChannel: channel,
|
|
},
|
|
{
|
|
ID: 2,
|
|
CollectionID: collectionID,
|
|
PartitionID: partitionID,
|
|
InsertChannel: channel,
|
|
},
|
|
{
|
|
ID: 3,
|
|
CollectionID: collectionID,
|
|
PartitionID: partitionID,
|
|
InsertChannel: channel,
|
|
},
|
|
}
|
|
suite.meta.PutCollection(ctx, utils.CreateTestCollection(collectionID, 1), utils.CreateTestPartition(collectionID, 1))
|
|
suite.broker.ExpectedCalls = nil
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, mock.Anything).Return([]*datapb.VchannelInfo{vchannel}, segments, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, collectionID)
|
|
suite.target.UpdateCollectionCurrentTarget(ctx, collectionID)
|
|
suite.target.UpdateCollectionNextTarget(ctx, collectionID)
|
|
|
|
suite.dist.ChannelDistManager.Update(2, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: collectionID,
|
|
ChannelName: channel,
|
|
},
|
|
Node: 2,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: 2,
|
|
CollectionID: collectionID,
|
|
Channel: channel,
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
},
|
|
})
|
|
task, err := NewChannelTask(context.Background(),
|
|
10*time.Second,
|
|
WrapIDSource(2),
|
|
collectionID,
|
|
suite.replica,
|
|
NewChannelAction(1, ActionTypeGrow, channel),
|
|
NewChannelAction(2, ActionTypeReduce, channel),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// new delegator distribution hasn't updated, block balance
|
|
suite.scheduler.preProcess(task)
|
|
suite.Equal(0, task.step)
|
|
|
|
suite.dist.ChannelDistManager.Update(1, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: collectionID,
|
|
ChannelName: channel,
|
|
},
|
|
Node: 1,
|
|
Version: 2,
|
|
View: &meta.LeaderView{
|
|
ID: 1,
|
|
CollectionID: collectionID,
|
|
Channel: channel,
|
|
Version: 2,
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
},
|
|
})
|
|
|
|
// new delegator distribution updated, task step up
|
|
suite.scheduler.preProcess(task)
|
|
suite.Equal(1, task.step)
|
|
|
|
suite.dist.ChannelDistManager.Update(2)
|
|
// old delegator removed
|
|
suite.scheduler.preProcess(task)
|
|
suite.Equal(2, task.step)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestGetTasksJSON() {
|
|
ctx := context.Background()
|
|
scheduler := suite.newScheduler()
|
|
|
|
// Add some tasks to the scheduler
|
|
task1, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(1, ActionTypeGrow, "", 1),
|
|
)
|
|
suite.NoError(err)
|
|
err = scheduler.Add(task1)
|
|
suite.NoError(err)
|
|
|
|
task2, err := NewChannelTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
NewChannelAction(1, ActionTypeGrow, "channel-1"),
|
|
)
|
|
suite.NoError(err)
|
|
err = scheduler.Add(task2)
|
|
suite.NoError(err)
|
|
|
|
actualJSON := scheduler.GetTasksJSON()
|
|
|
|
var tasks []*metricsinfo.QueryCoordTask
|
|
err = json.Unmarshal([]byte(actualJSON), &tasks)
|
|
suite.NoError(err)
|
|
suite.Equal(2, len(tasks))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestCalculateTaskDelta() {
|
|
ctx := context.Background()
|
|
scheduler := suite.newScheduler()
|
|
|
|
coll := int64(1001)
|
|
nodeID := int64(1)
|
|
channelName := "channel-1"
|
|
segmentID := int64(1)
|
|
// add segment task for collection
|
|
task1, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(nodeID, ActionTypeGrow, "", segmentID, querypb.DataScope_Historical, 100),
|
|
)
|
|
task1.SetID(1)
|
|
suite.NoError(err)
|
|
err = scheduler.Add(task1)
|
|
suite.NoError(err)
|
|
task2, err := NewChannelTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
NewChannelAction(nodeID, ActionTypeGrow, channelName),
|
|
)
|
|
task2.SetID(2)
|
|
suite.NoError(err)
|
|
err = scheduler.Add(task2)
|
|
suite.NoError(err)
|
|
|
|
coll2 := int64(1005)
|
|
nodeID2 := int64(2)
|
|
channelName2 := "channel-2"
|
|
segmentID2 := int64(2)
|
|
task3, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll2,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(nodeID2, ActionTypeGrow, "", segmentID2, querypb.DataScope_Historical, 100),
|
|
)
|
|
suite.NoError(err)
|
|
task3.SetID(3)
|
|
err = scheduler.Add(task3)
|
|
suite.NoError(err)
|
|
task4, err := NewChannelTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll2,
|
|
suite.replica,
|
|
NewChannelAction(nodeID2, ActionTypeGrow, channelName2),
|
|
)
|
|
suite.NoError(err)
|
|
task4.SetID(4)
|
|
err = scheduler.Add(task4)
|
|
suite.NoError(err)
|
|
|
|
snapshot := scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll)
|
|
snapshot2 := scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll2)
|
|
|
|
// check task delta with collectionID and nodeID
|
|
suite.Equal(100, snapshot.GetByNodeInCollection(nodeID))
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID, coll))
|
|
suite.Equal(100, snapshot2.GetByNodeInCollection(nodeID2))
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID2, coll2))
|
|
|
|
// check task delta with collectionID=-1
|
|
suite.Equal(100, snapshot.GetByNode(nodeID))
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID, -1))
|
|
suite.Equal(100, snapshot.GetByNode(nodeID2))
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID2, -1))
|
|
|
|
// check task delta with nodeID=-1
|
|
suite.Equal(100, snapshot.GetByNodeInCollection(nodeID)+snapshot.GetByNodeInCollection(nodeID2))
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(-1, coll))
|
|
suite.Equal(100, snapshot.GetByNodeInCollection(nodeID)+snapshot.GetByNodeInCollection(nodeID2))
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(-1, coll))
|
|
|
|
// check task delta with nodeID=-1 and collectionID=-1
|
|
suite.Equal(200, snapshot.GetByNode(nodeID)+snapshot.GetByNode(nodeID2))
|
|
suite.Equal(2, scheduler.GetChannelTaskDelta(-1, -1))
|
|
suite.Equal(200, snapshot.GetByNode(nodeID)+snapshot.GetByNode(nodeID2))
|
|
suite.Equal(2, scheduler.GetChannelTaskDelta(-1, -1))
|
|
|
|
scheduler.remove(task1)
|
|
scheduler.remove(task2)
|
|
scheduler.remove(task3)
|
|
scheduler.remove(task4)
|
|
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll)
|
|
snapshot2 = scheduler.GetSegmentTaskDeltaSnapshot([]int64{nodeID, nodeID2}, coll2)
|
|
suite.Equal(0, snapshot.GetByNodeInCollection(nodeID))
|
|
suite.Equal(0, scheduler.GetChannelTaskDelta(nodeID, coll))
|
|
suite.Equal(0, snapshot2.GetByNodeInCollection(nodeID2))
|
|
suite.Equal(0, scheduler.GetChannelTaskDelta(nodeID2, coll2))
|
|
|
|
task5, err := NewChannelTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll2,
|
|
suite.replica,
|
|
NewChannelAction(nodeID2, ActionTypeGrow, channelName2),
|
|
)
|
|
suite.NoError(err)
|
|
task4.SetID(5)
|
|
scheduler.incExecutingTaskDelta(task5)
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(nodeID2, coll2))
|
|
scheduler.decExecutingTaskDelta(task5)
|
|
suite.Equal(0, scheduler.GetChannelTaskDelta(nodeID2, coll2))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskDeltaWithDistFilter() {
|
|
ctx := context.Background()
|
|
scheduler := suite.newScheduler()
|
|
|
|
coll := int64(1001)
|
|
partition := int64(100)
|
|
channel := "channel-1"
|
|
sourceNode := int64(1)
|
|
targetNode := int64(2)
|
|
growSegmentID := int64(101)
|
|
reduceSegmentID := int64(102)
|
|
rowCount := 100
|
|
|
|
growTask, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(targetNode, ActionTypeGrow, "", growSegmentID, querypb.DataScope_Historical, rowCount),
|
|
)
|
|
suite.NoError(err)
|
|
growTask.SetID(1)
|
|
scheduler.incExecutingTaskDelta(growTask)
|
|
|
|
snapshot := scheduler.GetSegmentTaskDeltaSnapshot([]int64{targetNode}, coll)
|
|
suite.Equal(rowCount, snapshot.GetByNode(targetNode))
|
|
suite.Equal(rowCount, snapshot.GetByNodeInCollection(targetNode))
|
|
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{targetNode + 1}, coll)
|
|
suite.Equal(0, snapshot.GetByNode(targetNode))
|
|
|
|
suite.dist.SegmentDistManager.Update(targetNode,
|
|
utils.CreateTestSegment(coll, partition, growSegmentID, targetNode, 1, channel))
|
|
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{targetNode}, coll)
|
|
suite.Equal(0, snapshot.GetByNode(targetNode))
|
|
suite.Equal(0, snapshot.GetByNodeInCollection(targetNode))
|
|
|
|
scheduler.decExecutingTaskDelta(growTask)
|
|
|
|
suite.dist.SegmentDistManager.Update(sourceNode,
|
|
utils.CreateTestSegment(coll, partition, reduceSegmentID, sourceNode, 1, channel))
|
|
reduceTask, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(sourceNode, ActionTypeReduce, "", reduceSegmentID, querypb.DataScope_Historical, rowCount),
|
|
)
|
|
suite.NoError(err)
|
|
reduceTask.SetID(2)
|
|
scheduler.incExecutingTaskDelta(reduceTask)
|
|
|
|
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
|
|
suite.Equal(-rowCount, snapshot.GetByNode(sourceNode))
|
|
suite.Equal(-rowCount, snapshot.GetByNodeInCollection(sourceNode))
|
|
|
|
suite.dist.SegmentDistManager.Update(sourceNode)
|
|
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
|
|
suite.Equal(0, snapshot.GetByNode(sourceNode))
|
|
suite.Equal(0, snapshot.GetByNodeInCollection(sourceNode))
|
|
|
|
channelTask, err := NewChannelTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
NewChannelAction(targetNode, ActionTypeGrow, channel),
|
|
)
|
|
suite.NoError(err)
|
|
channelTask.SetID(3)
|
|
scheduler.incExecutingTaskDelta(channelTask)
|
|
suite.Equal(1, scheduler.GetChannelTaskDelta(targetNode, coll))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskDeltaSnapshotKeepsStreamingReduceUntilGrowingDistGone() {
|
|
ctx := context.Background()
|
|
scheduler := suite.newScheduler()
|
|
|
|
coll := int64(1001)
|
|
partition := int64(100)
|
|
channel := "channel-1"
|
|
sourceNode := int64(1)
|
|
segmentID := int64(102)
|
|
rowCount := 100
|
|
|
|
suite.dist.ChannelDistManager.Update(sourceNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: coll,
|
|
ChannelName: channel,
|
|
},
|
|
Node: sourceNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{
|
|
ID: sourceNode,
|
|
CollectionID: coll,
|
|
Channel: channel,
|
|
GrowingSegments: map[int64]*meta.Segment{
|
|
segmentID: utils.CreateTestSegment(coll, partition, segmentID, sourceNode, 1, channel),
|
|
},
|
|
},
|
|
})
|
|
|
|
reduceTask, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(sourceNode, ActionTypeReduce, channel, segmentID, querypb.DataScope_Streaming, rowCount),
|
|
)
|
|
suite.NoError(err)
|
|
reduceTask.SetID(1)
|
|
scheduler.incExecutingTaskDelta(reduceTask)
|
|
|
|
snapshot := scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
|
|
suite.Equal(-rowCount, snapshot.GetByNode(sourceNode))
|
|
suite.Equal(-rowCount, snapshot.GetByNodeInCollection(sourceNode))
|
|
|
|
suite.dist.ChannelDistManager.Update(sourceNode)
|
|
snapshot = scheduler.GetSegmentTaskDeltaSnapshot([]int64{sourceNode}, coll)
|
|
suite.Equal(0, snapshot.GetByNode(sourceNode))
|
|
suite.Equal(0, snapshot.GetByNodeInCollection(sourceNode))
|
|
|
|
scheduler.decExecutingTaskDelta(reduceTask)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestChannelTaskDeltaCache() {
|
|
delta := NewChannelTaskDelta()
|
|
|
|
taskDelta := []int{1, 2, 3, 4, 5, -6, -7, -8, -9, -10}
|
|
|
|
nodeID := int64(1)
|
|
collectionID := int64(100)
|
|
|
|
tasks := make([]Task, 0)
|
|
for i := 0; i < len(taskDelta); i++ {
|
|
task, _ := NewChannelTask(
|
|
context.TODO(),
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
collectionID,
|
|
suite.replica,
|
|
NewChannelAction(nodeID, ActionTypeGrow, "channel"),
|
|
)
|
|
task.SetID(int64(i))
|
|
tasks = append(tasks, task)
|
|
}
|
|
|
|
tasks = lo.Shuffle(tasks)
|
|
for i := 0; i < len(taskDelta); i++ {
|
|
delta.Add(tasks[i].(*ChannelTask))
|
|
}
|
|
|
|
tasks = lo.Shuffle(tasks)
|
|
for i := 0; i < len(taskDelta); i++ {
|
|
delta.Sub(tasks[i].(*ChannelTask))
|
|
}
|
|
suite.Equal(0, delta.Get(nodeID, collectionID))
|
|
suite.Equal(0, delta.Get(nodeID, -1))
|
|
suite.Equal(0, delta.Get(-1, -1))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestRemoveTaskWithError() {
|
|
ctx := context.Background()
|
|
scheduler := suite.newScheduler()
|
|
|
|
mockTarget := meta.NewMockTargetManager(suite.T())
|
|
mockTarget.EXPECT().UpdateCollectionNextTarget(mock.Anything, mock.Anything).Return(nil)
|
|
scheduler.targetMgr = mockTarget
|
|
|
|
coll := int64(1001)
|
|
nodeID := int64(1)
|
|
// add segment task for collection
|
|
task1, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(nodeID, ActionTypeGrow, "", 1, querypb.DataScope_Historical, 100),
|
|
)
|
|
suite.NoError(err)
|
|
err = scheduler.Add(task1)
|
|
suite.NoError(err)
|
|
|
|
task1.Fail(merr.ErrSegmentNotFound)
|
|
// when try to remove task with ErrSegmentNotFound, should trigger UpdateNextTarget
|
|
scheduler.remove(task1)
|
|
mockTarget.AssertExpectations(suite.T())
|
|
|
|
// test remove task with ErrSegmentRequestResourceFailed
|
|
task2, err := NewSegmentTask(
|
|
ctx,
|
|
10*time.Second,
|
|
WrapIDSource(0),
|
|
coll,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(nodeID, ActionTypeGrow, "", 1, querypb.DataScope_Historical, 100),
|
|
)
|
|
suite.NoError(err)
|
|
err = scheduler.Add(task2)
|
|
suite.NoError(err)
|
|
|
|
task2.Fail(merr.ErrSegmentRequestResourceFailed)
|
|
paramtable.Get().Save(paramtable.Get().QueryCoordCfg.ResourceExhaustionPenaltyDuration.Key, "3")
|
|
scheduler.remove(task2)
|
|
suite.True(suite.nodeMgr.IsResourceExhausted(nodeID))
|
|
// expect the penalty duration is expired
|
|
time.Sleep(3 * time.Second)
|
|
suite.False(suite.nodeMgr.IsResourceExhausted(nodeID))
|
|
}
|
|
|
|
func TestTask(t *testing.T) {
|
|
suite.Run(t, new(TaskSuite))
|
|
}
|
|
|
|
func TestSegmentActionFinishStates(t *testing.T) {
|
|
dist := meta.NewDistributionManager(nil)
|
|
action := NewSegmentActionWithScope(1, ActionTypeGrow, "ch", 10, querypb.DataScope_Historical, 100)
|
|
|
|
assert.False(t, action.IsFinished(dist))
|
|
|
|
action.rpcReturned.Store(true)
|
|
assert.False(t, action.IsFinished(dist))
|
|
|
|
dist.SegmentDistManager.Update(1, utils.CreateTestSegment(100, 1, 10, 1, 1, "ch"))
|
|
assert.True(t, action.IsFinished(dist))
|
|
|
|
updateAction := NewSegmentActionWithScope(1, ActionTypeUpdate, "ch", 10, querypb.DataScope_Historical, 0)
|
|
updateAction.rpcReturned.Store(true)
|
|
assert.True(t, updateAction.IsFinished(dist))
|
|
}
|
|
|
|
func TestSegmentTaskDeltaSnapshotDefaults(t *testing.T) {
|
|
snapshot := NewSegmentTaskDeltaSnapshot(nil, nil)
|
|
assert.Equal(t, 0, snapshot.GetByNode(1))
|
|
assert.Equal(t, 0, snapshot.GetByNodeInCollection(1))
|
|
|
|
var nilSnapshot *SegmentTaskDeltaSnapshot
|
|
assert.Equal(t, 0, nilSnapshot.GetByNode(1))
|
|
assert.Equal(t, 0, nilSnapshot.GetByNodeInCollection(1))
|
|
}
|
|
|
|
func TestSegmentTaskDeltaDefensiveBranches(t *testing.T) {
|
|
replica := newReplicaDefaultRG(10)
|
|
segmentTask, err := NewSegmentTask(
|
|
context.Background(),
|
|
time.Second,
|
|
WrapIDSource(0),
|
|
100,
|
|
replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentActionWithScope(1, ActionTypeGrow, "ch", 10, querypb.DataScope_Historical, 100),
|
|
)
|
|
assert.NoError(t, err)
|
|
segmentTask.SetID(1)
|
|
|
|
delta := NewSegmentTaskDelta()
|
|
delta.Add(segmentTask)
|
|
delta.printDetailInfos()
|
|
delta.Add(segmentTask)
|
|
assert.Len(t, delta.records[segmentTask.ID()], 1)
|
|
|
|
delta.Sub(segmentTask)
|
|
delta.Sub(segmentTask)
|
|
assert.Empty(t, delta.records)
|
|
|
|
base := newBaseTask(context.Background(), WrapIDSource(0), 100, replica, "ch", "MalformedSegmentTask")
|
|
base.SetID(2)
|
|
base.actions = []Action{NewChannelAction(1, ActionTypeGrow, "ch")}
|
|
malformedTask := &SegmentTask{baseTask: base, segmentID: 10}
|
|
|
|
delta.Add(malformedTask)
|
|
assert.Empty(t, delta.records[malformedTask.ID()])
|
|
|
|
dist := meta.NewDistributionManager(nil)
|
|
assert.False(t, segmentDeltaRecord{segmentID: 0}.isSegmentDistMatched(dist))
|
|
assert.True(t, segmentDeltaRecord{nodeID: 1, segmentID: 10, actionType: ActionTypeUpdate}.isSegmentDistMatched(dist))
|
|
}
|
|
|
|
func TestChannelTaskDeltaDefensiveBranches(t *testing.T) {
|
|
replica := newReplicaDefaultRG(10)
|
|
channelTask, err := NewChannelTask(
|
|
context.Background(),
|
|
time.Second,
|
|
WrapIDSource(0),
|
|
100,
|
|
replica,
|
|
NewChannelAction(1, ActionTypeGrow, "ch"),
|
|
)
|
|
assert.NoError(t, err)
|
|
channelTask.SetID(1)
|
|
|
|
delta := NewChannelTaskDelta()
|
|
delta.Add(channelTask)
|
|
delta.printDetailInfos()
|
|
delta.Add(channelTask)
|
|
assert.Equal(t, 1, delta.Get(1, 100))
|
|
|
|
delta.Sub(channelTask)
|
|
delta.Sub(channelTask)
|
|
assert.Equal(t, 0, delta.Get(1, 100))
|
|
|
|
delta.taskIDRecords.Insert(channelTask.ID())
|
|
delete(delta.data, int64(1))
|
|
delta.Sub(channelTask)
|
|
assert.Equal(t, -1, delta.Get(1, 100))
|
|
}
|
|
|
|
func TestMockSchedulerGetSegmentTaskDeltaSnapshot(t *testing.T) {
|
|
nodes := []int64{1, 2}
|
|
expected := NewSegmentTaskDeltaSnapshot(map[int64]int{1: 10}, map[int64]int{1: 5})
|
|
|
|
mockScheduler := NewMockScheduler(t)
|
|
mockScheduler.EXPECT().
|
|
GetSegmentTaskDeltaSnapshot(nodes, int64(100)).
|
|
Run(func(nodeIDs []int64, collectionID int64) {
|
|
assert.Equal(t, nodes, nodeIDs)
|
|
assert.Equal(t, int64(100), collectionID)
|
|
}).
|
|
Return(expected).
|
|
Once()
|
|
assert.Same(t, expected, mockScheduler.GetSegmentTaskDeltaSnapshot(nodes, 100))
|
|
|
|
mockScheduler.EXPECT().
|
|
GetSegmentTaskDeltaSnapshot(mock.Anything, int64(101)).
|
|
RunAndReturn(func(nodeIDs []int64, collectionID int64) *SegmentTaskDeltaSnapshot {
|
|
assert.Equal(t, nodes, nodeIDs)
|
|
assert.Equal(t, int64(101), collectionID)
|
|
return NewSegmentTaskDeltaSnapshot(map[int64]int{2: 20}, map[int64]int{2: 15})
|
|
}).
|
|
Once()
|
|
|
|
snapshot := mockScheduler.GetSegmentTaskDeltaSnapshot(nodes, 101)
|
|
assert.Equal(t, 20, snapshot.GetByNode(2))
|
|
assert.Equal(t, 15, snapshot.GetByNodeInCollection(2))
|
|
}
|
|
|
|
func TestMockSchedulerGetSegmentTaskDeltaSnapshotPanicsWithoutReturn(t *testing.T) {
|
|
mockScheduler := NewMockScheduler(t)
|
|
mockScheduler.On("GetSegmentTaskDeltaSnapshot", mock.Anything, int64(100))
|
|
|
|
assert.Panics(t, func() {
|
|
mockScheduler.GetSegmentTaskDeltaSnapshot([]int64{1}, 100)
|
|
})
|
|
}
|
|
|
|
func newReplicaDefaultRG(replicaID int64) *meta.Replica {
|
|
return meta.NewReplica(
|
|
&querypb.Replica{
|
|
ID: replicaID,
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
},
|
|
typeutil.NewUniqueSet(),
|
|
)
|
|
}
|
|
|
|
func (suite *TaskSuite) TestSegmentTaskShardLeaderID() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
|
|
// Create a segment task
|
|
action := NewSegmentActionWithScope(1, ActionTypeGrow, "", 100, querypb.DataScope_Historical, 100)
|
|
segmentTask, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
action,
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Test initial shard leader ID (should be -1)
|
|
suite.Equal(int64(-1), segmentTask.ShardLeaderID())
|
|
|
|
// Test setting shard leader ID
|
|
expectedLeaderID := int64(123)
|
|
segmentTask.SetShardLeaderID(expectedLeaderID)
|
|
suite.Equal(expectedLeaderID, segmentTask.ShardLeaderID())
|
|
|
|
// Test setting another value
|
|
anotherLeaderID := int64(456)
|
|
segmentTask.SetShardLeaderID(anotherLeaderID)
|
|
suite.Equal(anotherLeaderID, segmentTask.ShardLeaderID())
|
|
|
|
// Test with zero value
|
|
segmentTask.SetShardLeaderID(0)
|
|
suite.Equal(int64(0), segmentTask.ShardLeaderID())
|
|
}
|
|
|
|
func (suite *TaskSuite) TestExecutor_MoveSegmentTask() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
sourceNode := int64(2)
|
|
targetNode := int64(3)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
suite.meta.PutCollection(ctx, utils.CreateTestCollection(suite.collection, 1))
|
|
suite.meta.Put(ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{sourceNode, targetNode}))
|
|
|
|
// Create move task with both grow and reduce actions to simulate TaskTypeMove
|
|
segmentID := suite.loadSegments[0]
|
|
growAction := NewSegmentAction(targetNode, ActionTypeGrow, channel.ChannelName, segmentID)
|
|
reduceAction := NewSegmentAction(sourceNode, ActionTypeReduce, channel.ChannelName, segmentID)
|
|
|
|
// Create a move task that has both actions
|
|
moveTask, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
growAction,
|
|
reduceAction,
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Mock cluster expectations for load segment
|
|
suite.cluster.EXPECT().LoadSegments(mock.Anything, targetNode, mock.Anything).Return(merr.Success(), nil)
|
|
suite.cluster.EXPECT().ReleaseSegments(mock.Anything, mock.Anything, mock.Anything).Return(merr.Success(), nil)
|
|
|
|
suite.broker.EXPECT().DescribeCollection(mock.Anything, suite.collection).RunAndReturn(func(ctx context.Context, i int64) (*milvuspb.DescribeCollectionResponse, error) {
|
|
return &milvuspb.DescribeCollectionResponse{
|
|
Schema: &schemapb.CollectionSchema{
|
|
Name: "TestMoveSegmentTask",
|
|
Fields: []*schemapb.FieldSchema{
|
|
{FieldID: 100, Name: "vec", DataType: schemapb.DataType_FloatVector},
|
|
},
|
|
},
|
|
}, nil
|
|
})
|
|
suite.broker.EXPECT().ListIndexes(mock.Anything, suite.collection).Return([]*indexpb.IndexInfo{
|
|
{
|
|
CollectionID: suite.collection,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetSegmentInfo(mock.Anything, segmentID).Return([]*datapb.SegmentInfo{
|
|
{
|
|
ID: segmentID,
|
|
CollectionID: suite.collection,
|
|
PartitionID: -1,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}, nil)
|
|
suite.broker.EXPECT().GetIndexInfo(mock.Anything, suite.collection, segmentID).Return(nil, nil)
|
|
|
|
// Set up distribution with leader view
|
|
view := &meta.LeaderView{
|
|
ID: targetNode,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Segments: make(map[int64]*querypb.SegmentDist),
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
}
|
|
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: channel,
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: view,
|
|
})
|
|
|
|
// Add segments to original node distribution for release
|
|
segments := []*meta.Segment{
|
|
{
|
|
SegmentInfo: &datapb.SegmentInfo{
|
|
ID: segmentID,
|
|
CollectionID: suite.collection,
|
|
PartitionID: 1,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
},
|
|
}
|
|
suite.dist.SegmentDistManager.Update(sourceNode, segments...)
|
|
|
|
// Set up broker expectations
|
|
segmentInfos := []*datapb.SegmentInfo{
|
|
{
|
|
ID: segmentID,
|
|
CollectionID: suite.collection,
|
|
PartitionID: 1,
|
|
InsertChannel: channel.ChannelName,
|
|
},
|
|
}
|
|
suite.broker.EXPECT().GetRecoveryInfoV2(mock.Anything, suite.collection).Return([]*datapb.VchannelInfo{channel}, segmentInfos, nil)
|
|
suite.target.UpdateCollectionNextTarget(ctx, suite.collection)
|
|
|
|
// Test that move task sets shard leader ID during load step
|
|
suite.Equal(TaskTypeMove, GetTaskType(moveTask))
|
|
suite.Equal(int64(-1), moveTask.ShardLeaderID()) // Initial value
|
|
|
|
// Set up task executor
|
|
executor := NewExecutor(targetNode,
|
|
suite.meta,
|
|
suite.dist,
|
|
suite.broker,
|
|
suite.target,
|
|
suite.cluster,
|
|
suite.nodeMgr,
|
|
)
|
|
|
|
// Verify shard leader ID was set for load action in move task
|
|
executor.executeSegmentAction(moveTask, 0)
|
|
suite.Equal(targetNode, moveTask.ShardLeaderID())
|
|
suite.NoError(moveTask.Err())
|
|
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, 1, segmentID, targetNode, 1, channel.ChannelName))
|
|
suite.True(moveTask.actions[0].IsFinished(suite.dist))
|
|
|
|
// expect release action will execute successfully
|
|
executor.executeSegmentAction(moveTask, 1)
|
|
suite.Equal(targetNode, moveTask.ShardLeaderID())
|
|
suite.dist.SegmentDistManager.Update(sourceNode)
|
|
suite.True(moveTask.actions[1].IsFinished(suite.dist))
|
|
suite.NoError(moveTask.Err())
|
|
|
|
// test shard leader change before release action
|
|
newLeaderID := sourceNode
|
|
view1 := &meta.LeaderView{
|
|
ID: newLeaderID,
|
|
CollectionID: suite.collection,
|
|
Channel: channel.ChannelName,
|
|
Segments: make(map[int64]*querypb.SegmentDist),
|
|
Status: &querypb.LeaderViewStatus{Serviceable: true},
|
|
Version: 100,
|
|
}
|
|
|
|
suite.dist.ChannelDistManager.Update(newLeaderID, &meta.DmChannel{
|
|
VchannelInfo: channel,
|
|
Node: newLeaderID,
|
|
Version: 100,
|
|
View: view1,
|
|
})
|
|
|
|
// expect release action will skip and task will fail
|
|
suite.broker.ExpectedCalls = nil
|
|
executor.executeSegmentAction(moveTask, 1)
|
|
suite.True(moveTask.actions[1].IsFinished(suite.dist))
|
|
suite.ErrorContains(moveTask.Err(), "shard leader changed")
|
|
}
|
|
|
|
func (suite *TaskSuite) TestLeaderTaskStaleByRONode() {
|
|
ctx := context.Background()
|
|
leaderNode := int64(1)
|
|
workerNode := int64(3)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Test case 1: LeaderAction should NOT be stale when worker node is RO but leader is RW
|
|
// This is the fix for issue #46737: the checkStale should use leaderID instead of Node() for LeaderAction
|
|
suite.Run("WorkerRONodeLeaderRW", func() {
|
|
// Create replica with worker node (node 3) as RO node, leader node (node 1) as RW
|
|
replicaWithRONode := meta.NewReplica(&querypb.Replica{
|
|
ID: suite.replica.GetID(),
|
|
CollectionID: suite.collection,
|
|
Nodes: []int64{1, 2}, // RW nodes (leader is RW)
|
|
RoNodes: []int64{3}, // RO nodes (worker is RO)
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
}, typeutil.NewUniqueSet(1, 2))
|
|
suite.meta.Put(suite.ctx, replicaWithRONode)
|
|
|
|
// Set up channel distribution
|
|
suite.dist.ChannelDistManager.Update(leaderNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: leaderNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: leaderNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
// Create a LeaderAction that syncs segment from worker (RO node) to leader (RW node)
|
|
// leaderID=1 (RW), workerID=3 (RO)
|
|
// Before fix: checkStale used action.Node() which returns workerNode (3, RO), causing false stale
|
|
// After fix: checkStale uses leaderID (1, RW), task should NOT be stale
|
|
task := NewLeaderSegmentTask(
|
|
ctx,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
replicaWithRONode,
|
|
leaderNode,
|
|
NewLeaderAction(leaderNode, workerNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[0], 0),
|
|
)
|
|
|
|
// Add task should succeed because leader node is RW
|
|
err := suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Task should NOT be stale because we check leader node status, not worker node
|
|
suite.Equal(TaskStatusStarted, task.Status())
|
|
suite.NoError(task.Err())
|
|
|
|
// Clean up
|
|
suite.scheduler.remove(task)
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
})
|
|
|
|
// Test case 2: LeaderAction should be stale when leader node becomes RO
|
|
suite.Run("LeaderRONode", func() {
|
|
// Set up channel distribution with leader on node 1
|
|
suite.dist.ChannelDistManager.Update(leaderNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: leaderNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: leaderNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
// Create task with original replica (all RW nodes)
|
|
task := NewLeaderSegmentTask(
|
|
ctx,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
leaderNode,
|
|
NewLeaderAction(leaderNode, workerNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[0], 0),
|
|
)
|
|
|
|
// Add task should succeed
|
|
err := suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Now change replica to make leader node (node 1) as RO node
|
|
replicaWithLeaderRO := meta.NewReplica(&querypb.Replica{
|
|
ID: suite.replica.GetID(),
|
|
CollectionID: suite.collection,
|
|
Nodes: []int64{2, 3}, // RW nodes
|
|
RoNodes: []int64{1}, // Leader becomes RO
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
}, typeutil.NewUniqueSet(2, 3))
|
|
suite.meta.Put(suite.ctx, replicaWithLeaderRO)
|
|
|
|
// Dispatch will trigger promote which calls checkStale
|
|
suite.dispatchAndWait(leaderNode)
|
|
|
|
// Task should be canceled because leader node is RO
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.ErrorContains(task.Err(), "node becomes ro node")
|
|
|
|
// Restore original replica
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
})
|
|
|
|
// Test case 3: LeaderAction with Reduce type should NOT be affected by RO node
|
|
suite.Run("LeaderReduceActionNotAffected", func() {
|
|
// Create replica with leader node as RO
|
|
replicaWithLeaderRO := meta.NewReplica(&querypb.Replica{
|
|
ID: suite.replica.GetID(),
|
|
CollectionID: suite.collection,
|
|
Nodes: []int64{2, 3}, // RW nodes
|
|
RoNodes: []int64{1}, // Leader is RO
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
}, typeutil.NewUniqueSet(2, 3))
|
|
suite.meta.Put(suite.ctx, replicaWithLeaderRO)
|
|
|
|
// Set up channel distribution
|
|
suite.dist.ChannelDistManager.Update(leaderNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: leaderNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: leaderNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
// Create LeaderAction with Reduce type
|
|
task := NewLeaderSegmentTask(
|
|
ctx,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
replicaWithLeaderRO,
|
|
leaderNode,
|
|
NewLeaderAction(leaderNode, workerNode, ActionTypeReduce, channel.GetChannelName(), suite.releaseSegments[0], 0),
|
|
)
|
|
|
|
// Add task should succeed because Reduce action is not affected by RO node check
|
|
err := suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Clean up
|
|
suite.scheduler.remove(task)
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
})
|
|
}
|
|
|
|
func (suite *TaskSuite) TestTaskStaleByRONode() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Test case 1: Task stale due to target node becomes RO node
|
|
suite.Run("RONode", func() {
|
|
// Set up channel distribution first with RW replica
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
// Create task with original replica (all RW nodes)
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[0]),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Add task should succeed
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Now change replica to make node 3 as RO node (simulating node state change)
|
|
replicaWithRONode := meta.NewReplica(&querypb.Replica{
|
|
ID: suite.replica.GetID(),
|
|
CollectionID: suite.collection,
|
|
Nodes: []int64{1, 2}, // RW nodes
|
|
RoNodes: []int64{3}, // RO node
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
}, typeutil.NewUniqueSet(1, 2))
|
|
suite.meta.Put(suite.ctx, replicaWithRONode)
|
|
|
|
// Dispatch will trigger promote which calls checkStale
|
|
suite.dispatchAndWait(targetNode)
|
|
|
|
// Task should be canceled due to RO node
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.ErrorContains(task.Err(), "node becomes ro node")
|
|
|
|
// Restore original replica for other tests
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
})
|
|
|
|
// Test case 2: Task stale due to target node becomes RO SQ node
|
|
suite.Run("ROSQNode", func() {
|
|
// Set up channel distribution
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
// Create task with original replica (all RW nodes)
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), suite.loadSegments[1]),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Add task should succeed
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Now change replica to make node 3 as RO SQ node
|
|
replicaWithROSQNode := meta.NewReplica(&querypb.Replica{
|
|
ID: suite.replica.GetID(),
|
|
CollectionID: suite.collection,
|
|
Nodes: []int64{1, 2}, // RW nodes
|
|
RoSqNodes: []int64{3}, // RO SQ node
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
}, typeutil.NewUniqueSet(1, 2))
|
|
suite.meta.Put(suite.ctx, replicaWithROSQNode)
|
|
|
|
// Dispatch will trigger promote which calls checkStale
|
|
suite.dispatchAndWait(targetNode)
|
|
|
|
// Task should be canceled due to RO SQ node
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.ErrorContains(task.Err(), "node becomes ro node")
|
|
|
|
// Restore original replica
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
})
|
|
|
|
// Test case 3: ActionTypeReduce should not be affected by RO node
|
|
suite.Run("ReduceActionNotAffected", func() {
|
|
// Create replica with node 3 as RO node
|
|
replicaWithRONode := meta.NewReplica(&querypb.Replica{
|
|
ID: suite.replica.GetID(),
|
|
CollectionID: suite.collection,
|
|
Nodes: []int64{1, 2}, // RW nodes
|
|
RoNodes: []int64{3}, // RO node
|
|
ResourceGroup: meta.DefaultResourceGroupName,
|
|
}, typeutil.NewUniqueSet(1, 2))
|
|
suite.meta.Put(suite.ctx, replicaWithRONode)
|
|
|
|
// Set up channel distribution
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
// Create a segment task with Reduce action targeting the RO node
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
replicaWithRONode,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), suite.releaseSegments[0]),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Add task should succeed because Reduce action is not affected by RO node check
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Clean up
|
|
suite.scheduler.remove(task)
|
|
suite.meta.Put(suite.ctx, utils.CreateTestReplica(suite.replica.GetID(), suite.collection, []int64{1, 2, 3}))
|
|
})
|
|
}
|
|
|
|
func (suite *TaskSuite) TestTaskStaleBySegmentInDist() {
|
|
ctx := context.Background()
|
|
timeout := 10 * time.Second
|
|
targetNode := int64(3)
|
|
channel := &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: Params.CommonCfg.RootCoordDml.GetValue() + "-test",
|
|
}
|
|
|
|
// Test case: Grow task should be stale when segment already exists in dist
|
|
suite.Run("SegmentAlreadyInDist", func() {
|
|
// Set up channel distribution
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
segmentID := suite.loadSegments[0]
|
|
|
|
// Create a Grow task for the segment
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeGrow, channel.GetChannelName(), segmentID),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Add task should succeed (segment not in dist yet)
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Now simulate segment being loaded: add segment to dist
|
|
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, 1, segmentID, targetNode, 1, channel.ChannelName))
|
|
|
|
// Dispatch will trigger promote which calls checkStale
|
|
suite.dispatchAndWait(targetNode)
|
|
|
|
// Task should be canceled because segment is already in dist
|
|
suite.Equal(TaskStatusCanceled, task.Status())
|
|
suite.ErrorContains(task.Err(), "segment already loaded in dist")
|
|
|
|
// Clean up
|
|
suite.dist.SegmentDistManager.Update(targetNode)
|
|
})
|
|
|
|
// Test case: Reduce task should not be affected by segment in dist check
|
|
suite.Run("ReduceTaskNotAffected", func() {
|
|
// Set up channel distribution
|
|
suite.dist.ChannelDistManager.Update(targetNode, &meta.DmChannel{
|
|
VchannelInfo: &datapb.VchannelInfo{
|
|
CollectionID: suite.collection,
|
|
ChannelName: channel.ChannelName,
|
|
},
|
|
Node: targetNode,
|
|
Version: 1,
|
|
View: &meta.LeaderView{ID: targetNode, CollectionID: suite.collection, Channel: channel.ChannelName, Status: &querypb.LeaderViewStatus{Serviceable: true}},
|
|
})
|
|
|
|
segmentID := suite.releaseSegments[0]
|
|
|
|
// Add segment to dist first
|
|
suite.dist.SegmentDistManager.Update(targetNode, utils.CreateTestSegment(suite.collection, 1, segmentID, targetNode, 1, channel.ChannelName))
|
|
|
|
// Create a Reduce task for the segment (should not be affected by dist check)
|
|
task, err := NewSegmentTask(
|
|
ctx,
|
|
timeout,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(targetNode, ActionTypeReduce, channel.GetChannelName(), segmentID),
|
|
)
|
|
suite.NoError(err)
|
|
|
|
// Add task should succeed - Reduce task is not affected by "segment in dist" check
|
|
err = suite.scheduler.Add(task)
|
|
suite.NoError(err)
|
|
|
|
// Clean up without dispatching (to avoid mock issues)
|
|
suite.scheduler.remove(task)
|
|
suite.dist.SegmentDistManager.Update(targetNode)
|
|
})
|
|
}
|
|
|
|
func (suite *TaskSuite) TestTaskQueueRangePriority() {
|
|
queue := newTaskQueue()
|
|
nodeID := int64(1)
|
|
|
|
taskLow, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 400),
|
|
)
|
|
suite.NoError(err)
|
|
taskLow.SetID(31)
|
|
taskLow.SetPriority(TaskPriorityLow)
|
|
|
|
taskNormal, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 401),
|
|
)
|
|
suite.NoError(err)
|
|
taskNormal.SetID(32)
|
|
taskNormal.SetPriority(TaskPriorityNormal)
|
|
|
|
taskHigh, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 402),
|
|
)
|
|
suite.NoError(err)
|
|
taskHigh.SetID(33)
|
|
taskHigh.SetPriority(TaskPriorityHigh)
|
|
|
|
queue.Add(taskLow)
|
|
queue.Add(taskHigh)
|
|
queue.Add(taskNormal)
|
|
|
|
suite.Equal(3, queue.Len())
|
|
|
|
var visited []Priority
|
|
queue.Range(func(t Task) bool {
|
|
visited = append(visited, t.Priority())
|
|
return true
|
|
})
|
|
suite.Equal([]Priority{TaskPriorityHigh, TaskPriorityNormal, TaskPriorityLow}, visited)
|
|
|
|
queue.Remove(taskHigh)
|
|
suite.Equal(2, queue.Len())
|
|
}
|
|
|
|
func (suite *TaskSuite) TestNodeTaskQueueNodeBucketing() {
|
|
queue := newNodeTaskQueue()
|
|
|
|
task1, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(1, ActionTypeGrow, "ch-0", 100),
|
|
)
|
|
suite.NoError(err)
|
|
task1.SetID(1)
|
|
|
|
task2, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(2, ActionTypeGrow, "ch-0", 101),
|
|
)
|
|
suite.NoError(err)
|
|
task2.SetID(2)
|
|
|
|
task3, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(1, ActionTypeGrow, "ch-0", 102),
|
|
)
|
|
suite.NoError(err)
|
|
task3.SetID(3)
|
|
|
|
queue.Add(task1)
|
|
queue.Add(task2)
|
|
queue.Add(task3)
|
|
|
|
suite.Equal(3, queue.Len())
|
|
suite.Equal(2, queue.LenByNode(1))
|
|
suite.Equal(1, queue.LenByNode(2))
|
|
suite.Equal(0, queue.LenByNode(999))
|
|
|
|
var node1Tasks []Task
|
|
queue.RangeByNode(1, func(task Task) bool {
|
|
node1Tasks = append(node1Tasks, task)
|
|
return true
|
|
})
|
|
suite.Equal(2, len(node1Tasks))
|
|
|
|
var node2Tasks []Task
|
|
queue.RangeByNode(2, func(task Task) bool {
|
|
node2Tasks = append(node2Tasks, task)
|
|
return true
|
|
})
|
|
suite.Equal(1, len(node2Tasks))
|
|
suite.Equal(int64(2), node2Tasks[0].ID())
|
|
|
|
queue.Remove(task1)
|
|
suite.Equal(2, queue.Len())
|
|
suite.Equal(1, queue.LenByNode(1))
|
|
suite.Equal(1, queue.LenByNode(2))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestNodeTaskQueueMoveTaskDualNode() {
|
|
queue := newNodeTaskQueue()
|
|
|
|
destNode := int64(1)
|
|
srcNode := int64(2)
|
|
segmentID := int64(200)
|
|
|
|
task, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(destNode, ActionTypeGrow, "ch-0", segmentID),
|
|
NewSegmentAction(srcNode, ActionTypeReduce, "ch-0", segmentID),
|
|
)
|
|
suite.NoError(err)
|
|
task.SetID(10)
|
|
|
|
queue.Add(task)
|
|
|
|
suite.Equal(1, queue.Len())
|
|
suite.Equal(1, queue.LenByNode(destNode))
|
|
suite.Equal(1, queue.LenByNode(srcNode))
|
|
|
|
var destTasks []Task
|
|
queue.RangeByNode(destNode, func(t Task) bool {
|
|
destTasks = append(destTasks, t)
|
|
return true
|
|
})
|
|
suite.Equal(1, len(destTasks))
|
|
suite.Equal(int64(10), destTasks[0].ID())
|
|
|
|
var srcTasks []Task
|
|
queue.RangeByNode(srcNode, func(t Task) bool {
|
|
srcTasks = append(srcTasks, t)
|
|
return true
|
|
})
|
|
suite.Equal(1, len(srcTasks))
|
|
suite.Equal(int64(10), srcTasks[0].ID())
|
|
|
|
queue.Remove(task)
|
|
suite.Equal(0, queue.Len())
|
|
suite.Equal(0, queue.LenByNode(destNode))
|
|
suite.Equal(0, queue.LenByNode(srcNode))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestNodeTaskQueueLeaderActionDualNode() {
|
|
queue := newNodeTaskQueue()
|
|
|
|
leaderID := int64(1)
|
|
workerID := int64(2)
|
|
segmentID := int64(300)
|
|
|
|
action := NewLeaderAction(leaderID, workerID, ActionTypeGrow, "ch-0", segmentID, 1)
|
|
task := NewLeaderSegmentTask(suite.ctx, WrapIDSource(0), suite.collection, suite.replica, leaderID, action)
|
|
task.SetID(20)
|
|
|
|
queue.Add(task)
|
|
|
|
suite.Equal(1, queue.Len())
|
|
suite.Equal(1, queue.LenByNode(workerID))
|
|
suite.Equal(1, queue.LenByNode(leaderID))
|
|
|
|
var workerTasks []Task
|
|
queue.RangeByNode(workerID, func(t Task) bool {
|
|
workerTasks = append(workerTasks, t)
|
|
return true
|
|
})
|
|
suite.Equal(1, len(workerTasks))
|
|
suite.Equal(int64(20), workerTasks[0].ID())
|
|
|
|
var leaderTasks []Task
|
|
queue.RangeByNode(leaderID, func(t Task) bool {
|
|
leaderTasks = append(leaderTasks, t)
|
|
return true
|
|
})
|
|
suite.Equal(1, len(leaderTasks))
|
|
suite.Equal(int64(20), leaderTasks[0].ID())
|
|
|
|
queue.Remove(task)
|
|
suite.Equal(0, queue.Len())
|
|
suite.Equal(0, queue.LenByNode(workerID))
|
|
suite.Equal(0, queue.LenByNode(leaderID))
|
|
}
|
|
|
|
func (suite *TaskSuite) TestNodeTaskQueueRangeByNodePriority() {
|
|
queue := newNodeTaskQueue()
|
|
nodeID := int64(1)
|
|
|
|
taskLow, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 400),
|
|
)
|
|
suite.NoError(err)
|
|
taskLow.SetID(31)
|
|
taskLow.SetPriority(TaskPriorityLow)
|
|
|
|
taskNormal, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 401),
|
|
)
|
|
suite.NoError(err)
|
|
taskNormal.SetID(32)
|
|
taskNormal.SetPriority(TaskPriorityNormal)
|
|
|
|
taskHigh, err := NewSegmentTask(
|
|
suite.ctx,
|
|
5*time.Second,
|
|
WrapIDSource(0),
|
|
suite.collection,
|
|
suite.replica,
|
|
commonpb.LoadPriority_LOW,
|
|
NewSegmentAction(nodeID, ActionTypeGrow, "ch-0", 402),
|
|
)
|
|
suite.NoError(err)
|
|
taskHigh.SetID(33)
|
|
taskHigh.SetPriority(TaskPriorityHigh)
|
|
|
|
queue.Add(taskLow)
|
|
queue.Add(taskHigh)
|
|
queue.Add(taskNormal)
|
|
|
|
suite.Equal(3, queue.LenByNode(nodeID))
|
|
|
|
var visited []Priority
|
|
queue.RangeByNode(nodeID, func(t Task) bool {
|
|
visited = append(visited, t.Priority())
|
|
return true
|
|
})
|
|
suite.Equal([]Priority{TaskPriorityHigh, TaskPriorityNormal, TaskPriorityLow}, visited)
|
|
}
|