Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:31:17 +08:00

295 lines
9.5 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 replication
import (
"context"
"io"
"testing"
"github.com/stretchr/testify/suite"
"google.golang.org/protobuf/proto"
"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/pkg/v3/common"
"github.com/milvus-io/milvus/pkg/v3/mlog"
"github.com/milvus-io/milvus/pkg/v3/util/funcutil"
"github.com/milvus-io/milvus/pkg/v3/util/merr"
"github.com/milvus-io/milvus/pkg/v3/util/paramtable"
"github.com/milvus-io/milvus/tests/integration"
)
type DataSalvageSuite struct {
integration.MiniClusterSuite
}
func TestDataSalvage(t *testing.T) {
suite.Run(t, new(DataSalvageSuite))
}
// TestGetReplicateInfoOnPrimaryCluster verifies that GetReplicateInfo
// returns checkpoint info on a primary cluster. The salvage checkpoint
// should be nil since no force promote has occurred.
func (s *DataSalvageSuite) TestGetReplicateInfoOnPrimaryCluster() {
ctx := context.Background()
clusterID := paramtable.Get().CommonCfg.ClusterPrefix.GetValue()
pchannel := clusterID + "-pchan0"
// First set up replication config to make the cluster a primary
config := &commonpb.ReplicateConfiguration{
Clusters: []*commonpb.MilvusCluster{
{
ClusterId: clusterID,
Pchannels: []string{pchannel},
ConnectionParam: &commonpb.ConnectionParam{
Uri: "localhost:19530",
Token: "test-token",
},
},
},
CrossClusterTopology: []*commonpb.CrossClusterTopology{},
}
updateResp, err := s.Cluster.MilvusClient.UpdateReplicateConfiguration(ctx, &milvuspb.UpdateReplicateConfigurationRequest{
ReplicateConfiguration: config,
ForcePromote: false,
})
s.NoError(err)
s.NoError(merr.Error(updateResp))
// Get replicate info
resp, err := s.Cluster.MilvusClient.GetReplicateInfo(ctx, &milvuspb.GetReplicateInfoRequest{
TargetPchannel: pchannel,
})
s.NoError(err)
// On a primary cluster, checkpoint should exist but salvage checkpoint should be nil
// (no force promote has occurred)
mlog.Info(context.TODO(), "GetReplicateInfo response",
mlog.Any("checkpoint", resp.GetCheckpoint()),
mlog.Any("salvageCheckpoint", resp.GetSalvageCheckpoint()))
// Salvage checkpoint should be nil on primary cluster
s.Nil(resp.GetSalvageCheckpoint(), "salvage checkpoint should be nil on primary cluster")
}
// TestDumpMessagesBasic verifies that DumpMessages can stream messages
// from a WAL channel after inserting some data.
func (s *DataSalvageSuite) TestDumpMessagesBasic() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const (
dim = 128
dbName = ""
rowNum = 100
)
collectionName := "TestDumpMessages" + funcutil.GenRandomStr()
// Create collection
schema := integration.ConstructSchemaOfVecDataType(collectionName, dim, true, schemapb.DataType_FloatVector)
marshaledSchema, err := proto.Marshal(schema)
s.NoError(err)
createResp, err := s.Cluster.MilvusClient.CreateCollection(ctx, &milvuspb.CreateCollectionRequest{
DbName: dbName,
CollectionName: collectionName,
Schema: marshaledSchema,
ShardsNum: common.DefaultShardsNum,
})
s.NoError(err)
s.Equal(commonpb.ErrorCode_Success, createResp.GetErrorCode())
// Insert some data
fVecColumn := integration.NewFloatVectorFieldData(integration.FloatVecField, rowNum, dim)
hashKeys := integration.GenerateHashKeys(rowNum)
insertResp, err := s.Cluster.MilvusClient.Insert(ctx, &milvuspb.InsertRequest{
DbName: dbName,
CollectionName: collectionName,
FieldsData: []*schemapb.FieldData{fVecColumn},
HashKeys: hashKeys,
NumRows: uint32(rowNum),
})
s.NoError(err)
s.Equal(commonpb.ErrorCode_Success, insertResp.GetStatus().GetErrorCode())
// Get pchannel for the collection
descResp, err := s.Cluster.MilvusClient.DescribeCollection(ctx, &milvuspb.DescribeCollectionRequest{
CollectionName: collectionName,
})
s.NoError(err)
s.NotEmpty(descResp.GetVirtualChannelNames())
// Get pchannel from vchannel
vchannel := descResp.GetVirtualChannelNames()[0]
pchannel := funcutil.ToPhysicalChannel(vchannel)
mlog.Info(context.TODO(), "Testing DumpMessages",
mlog.FieldPChannel(pchannel),
mlog.FieldVChannel(vchannel))
// Set up replication config first
clusterID := paramtable.Get().CommonCfg.ClusterPrefix.GetValue()
config := &commonpb.ReplicateConfiguration{
Clusters: []*commonpb.MilvusCluster{
{
ClusterId: clusterID,
Pchannels: []string{pchannel},
ConnectionParam: &commonpb.ConnectionParam{
Uri: "localhost:19530",
Token: "test-token",
},
},
},
CrossClusterTopology: []*commonpb.CrossClusterTopology{},
}
updateResp, err := s.Cluster.MilvusClient.UpdateReplicateConfiguration(ctx, &milvuspb.UpdateReplicateConfigurationRequest{
ReplicateConfiguration: config,
ForcePromote: false,
})
s.NoError(err)
s.NoError(merr.Error(updateResp))
// Get replicate checkpoint as start position
infoResp, err := s.Cluster.MilvusClient.GetReplicateInfo(ctx, &milvuspb.GetReplicateInfoRequest{
TargetPchannel: pchannel,
})
s.NoError(err)
s.NotNil(infoResp.GetCheckpoint())
s.NotNil(infoResp.GetCheckpoint().GetMessageId())
// Dump messages from start position (exclusive - messages after the checkpoint)
stream, err := s.Cluster.MilvusClient.DumpMessages(ctx, &milvuspb.DumpMessagesRequest{
Pchannel: pchannel,
StartMessageId: infoResp.GetCheckpoint().GetMessageId(),
})
s.NoError(err)
// Read some messages (with timeout via context cancellation)
messages := make([]*milvuspb.DumpMessagesResponse, 0)
readCtx, readCancel := context.WithCancel(ctx)
// Read in a goroutine with limit
go func() {
for i := 0; i < 10; i++ { // Read up to 10 messages
resp, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
mlog.Warn(context.TODO(), "error receiving message", mlog.Err(err))
break
}
// Check if response contains a message (not a status)
if msg := resp.GetMessage(); msg != nil {
messages = append(messages, resp)
mlog.Info(context.TODO(), "received message", mlog.String("messageId", msg.GetId().GetId()))
} else if status := resp.GetStatus(); status != nil {
mlog.Warn(context.TODO(), "received status response", mlog.Any("status", status))
break
}
}
readCancel()
}()
<-readCtx.Done()
mlog.Info(context.TODO(), "DumpMessages test completed", mlog.Int("messageCount", len(messages)))
// We should have received at least some messages
// Note: The exact count depends on timing and what messages are in the WAL
// For now, just verify the API works without error
// Clean up
dropResp, err := s.Cluster.MilvusClient.DropCollection(ctx, &milvuspb.DropCollectionRequest{
CollectionName: collectionName,
})
s.NoError(err)
s.Equal(commonpb.ErrorCode_Success, dropResp.GetErrorCode())
}
// TestDumpMessagesWithMissingPchannel verifies that DumpMessages returns
// an error when pchannel is not provided.
func (s *DataSalvageSuite) TestDumpMessagesWithMissingPchannel() {
ctx := context.Background()
stream, err := s.Cluster.MilvusClient.DumpMessages(ctx, &milvuspb.DumpMessagesRequest{
Pchannel: "", // Missing pchannel
StartMessageId: &commonpb.MessageID{
Id: "test",
},
})
// The error might come during stream creation or first Recv()
if err == nil {
_, err = stream.Recv()
}
s.Error(err)
}
// TestDumpMessagesWithMissingStartMessageId verifies that DumpMessages returns
// an error when start_message_id is not provided.
func (s *DataSalvageSuite) TestDumpMessagesWithMissingStartMessageId() {
ctx := context.Background()
stream, err := s.Cluster.MilvusClient.DumpMessages(ctx, &milvuspb.DumpMessagesRequest{
Pchannel: "test-pchannel",
StartMessageId: nil, // Missing start message ID
})
// The error might come during stream creation or first Recv()
if err == nil {
_, err = stream.Recv()
}
s.Error(err)
}
// TestDumpMessagesWithMalformedStartMessageId verifies that DumpMessages
// returns an error (instead of panicking and crashing the process) when
// start_message_id is non-empty but cannot be unmarshaled. See issue #50341.
func (s *DataSalvageSuite) TestDumpMessagesWithMalformedStartMessageId() {
ctx := context.Background()
stream, err := s.Cluster.MilvusClient.DumpMessages(ctx, &milvuspb.DumpMessagesRequest{
Pchannel: "test-pchannel",
StartMessageId: &commonpb.MessageID{
Id: "not-a-valid-base64-msgid!!!", // non-empty but undecodable
WALName: commonpb.WALName_Pulsar,
},
})
// The error might come during stream creation or first Recv()
if err == nil {
_, err = stream.Recv()
}
s.Error(err)
// The server must stay up: a follow-up RPC should still succeed.
_, err = s.Cluster.MilvusClient.GetReplicateInfo(ctx, &milvuspb.GetReplicateInfoRequest{
TargetPchannel: "test-pchannel",
})
s.NoError(err)
}