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

3766 lines
107 KiB
Go

// manager_test.go
package telemetry
import (
"context"
"encoding/json"
"fmt"
"sync"
"testing"
"time"
"github.com/cockroachdb/errors"
"github.com/stretchr/testify/assert"
"github.com/milvus-io/milvus-proto/go-api/v3/commonpb"
"github.com/milvus-io/milvus-proto/go-api/v3/milvuspb"
)
// mockCommandStore implements CommandStoreInterface for testing
type mockCommandStore struct {
mu sync.Mutex
commands map[string]*commonpb.ClientCommand
configs map[string]*ClientConfig
nextID int
// control flags for testing error paths
shouldFailPush bool
shouldFailList bool
shouldFailDelete bool
shouldFailListConfig bool
}
func newMockCommandStore() *mockCommandStore {
return &mockCommandStore{
commands: make(map[string]*commonpb.ClientCommand),
configs: make(map[string]*ClientConfig),
}
}
func (m *mockCommandStore) PushCommand(ctx context.Context, req *milvuspb.PushClientCommandRequest) (string, error) {
if m.shouldFailPush {
return "", errors.New("mock push error")
}
// Validate persistent flag usage
if req.Persistent && req.CommandType != "push_config" {
return "", fmt.Errorf("only push_config commands can be persistent, got: %s", req.CommandType)
}
m.mu.Lock()
defer m.mu.Unlock()
m.nextID++
idPrefix := "cmd"
if req.Persistent {
idPrefix = "cfg"
}
cmdID := fmt.Sprintf("%s-%d", idPrefix, m.nextID)
scope := "global"
if req.TargetClientId != "" {
scope = fmt.Sprintf("client:%s", req.TargetClientId)
}
if req.Persistent {
// Store as config
m.configs[cmdID] = &ClientConfig{
ConfigId: cmdID,
ConfigType: req.CommandType,
Payload: req.Payload,
CreateTime: time.Now().UnixMilli(),
TargetScope: scope,
}
} else {
// Store as regular command
m.commands[cmdID] = &commonpb.ClientCommand{
CommandId: cmdID,
CommandType: req.CommandType,
Payload: req.Payload,
CreateTime: time.Now().UnixMilli(),
TargetScope: scope,
}
}
return cmdID, nil
}
func (m *mockCommandStore) ListCommands(ctx context.Context) ([]*commonpb.ClientCommand, error) {
if m.shouldFailList {
return nil, errors.New("mock list error")
}
m.mu.Lock()
defer m.mu.Unlock()
var commands []*commonpb.ClientCommand
for _, cmd := range m.commands {
commands = append(commands, cmd)
}
return commands, nil
}
func (m *mockCommandStore) DeleteCommand(ctx context.Context, commandID string) error {
if m.shouldFailDelete {
return errors.New("mock delete error")
}
m.mu.Lock()
defer m.mu.Unlock()
// Try deleting from both commands and configs
delete(m.commands, commandID)
delete(m.configs, commandID)
return nil
}
func (m *mockCommandStore) DeleteNonPersistentCommand(commandID string) bool {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.commands[commandID]; ok {
delete(m.commands, commandID)
return true
}
return false
}
func (m *mockCommandStore) GetCommandInfo(commandID string) (string, []byte, bool, bool) {
m.mu.Lock()
defer m.mu.Unlock()
if cmd, ok := m.commands[commandID]; ok {
return cmd.CommandType, cmd.Payload, false, true
}
if cfg, ok := m.configs[commandID]; ok {
return cfg.ConfigType, cfg.Payload, true, true
}
return "", nil, false, false
}
func (m *mockCommandStore) CleanupExpiredCommands(ctx context.Context) {
// no-op for mock
}
func (m *mockCommandStore) ListConfigs(ctx context.Context) ([]*ClientConfig, string, error) {
if m.shouldFailListConfig {
return nil, "", errors.New("mock list config error")
}
m.mu.Lock()
defer m.mu.Unlock()
var configs []*ClientConfig
for _, cfg := range m.configs {
configs = append(configs, cfg)
}
// Simple hash calculation for testing
hash := ""
if len(configs) > 0 {
hash = "mock-config-hash"
}
return configs, hash, nil
}
func (m *mockCommandStore) ListCommandsWithInfo(ctx context.Context) ([]*CommandInfoData, error) {
if m.shouldFailList {
return nil, errors.New("mock list error")
}
m.mu.Lock()
defer m.mu.Unlock()
var result []*CommandInfoData
// Add commands (non-persistent)
for _, cmd := range m.commands {
result = append(result, &CommandInfoData{
CommandID: cmd.CommandId,
CommandType: cmd.CommandType,
TargetScope: cmd.TargetScope,
Persistent: false,
CreateTime: cmd.CreateTime,
TTLSeconds: 0, // mock doesn't track TTL
})
}
// Add configs (persistent)
for _, cfg := range m.configs {
result = append(result, &CommandInfoData{
CommandID: cfg.ConfigId,
CommandType: cfg.ConfigType,
TargetScope: cfg.TargetScope,
Persistent: true,
CreateTime: cfg.CreateTime,
TTLSeconds: 0,
})
}
return result, nil
}
func TestNewTelemetryManager(t *testing.T) {
mgr := NewTelemetryManager(nil) // nil etcd client for now
assert.NotNil(t, mgr)
// sync.Map is value type, always initialized
assert.NotNil(t, mgr.config)
}
func TestHandleHeartbeat(t *testing.T) {
mgr := NewTelemetryManager(nil)
req := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
SdkVersion: "2.4.0",
Host: "192.168.1.100",
},
ReportTimestamp: time.Now().UnixMilli(),
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
SuccessCount: 99,
ErrorCount: 1,
AvgLatencyMs: 15.5,
P99LatencyMs: 45.0,
},
},
},
ConfigHash: "",
LastCommandTimestamp: 0,
}
resp, err := mgr.HandleHeartbeat(req)
assert.NoError(t, err)
assert.NotNil(t, resp)
assert.True(t, resp.ServerTimestamp > 0)
// Verify client was registered
clients := mgr.ListClients("")
assert.Len(t, clients, 1)
assert.Equal(t, "pymilvus", clients[0].ClientInfo.SdkType)
}
func TestGetClientTelemetry(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register two clients via heartbeat
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "client-1"},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 100, SuccessCount: 100}},
},
})
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "java-sdk",
Host: "192.168.1.101",
Reserved: map[string]string{"client_id": "client-2"},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 50, SuccessCount: 50}},
},
})
// Query all clients
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 2)
assert.Equal(t, int64(150), resp.Aggregated.RequestCount)
// Query by client_id
resp, err = mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "client-1",
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
}
func TestMatchesScope(t *testing.T) {
mgr := NewTelemetryManager(nil)
clientInfo := &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
}
tests := []struct {
name string
scope string
clientID string
expected bool
}{
{"global scope matches any client", "global", "any-client", true},
{"client scope matches same client", "client:client-123", "client-123", true},
{"client scope does not match different client", "client:client-123", "client-456", false},
{"unknown scope does not match", "unknown:value", "any-client", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := mgr.matchesScope(tt.scope, tt.clientID, clientInfo)
assert.Equal(t, tt.expected, result)
})
}
}
func TestMatchesScopeDatabase(t *testing.T) {
mgr := NewTelemetryManager(nil)
clientInfo := &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
}
t.Run("database scope matches client with access", func(t *testing.T) {
// Register a client with database access
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{
"client_id": "db-client-1",
"db_name": "test_database",
},
},
})
result := mgr.matchesScope("database:test_database", "db-client-1", clientInfo)
assert.True(t, result)
})
t.Run("database scope does not match client without access", func(t *testing.T) {
// Register a client with different database access
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.101",
Reserved: map[string]string{
"client_id": "db-client-2",
"db_name": "other_database",
},
},
})
result := mgr.matchesScope("database:test_database", "db-client-2", clientInfo)
assert.False(t, result)
})
t.Run("database scope does not match unknown client", func(t *testing.T) {
result := mgr.matchesScope("database:test_database", "unknown-client", clientInfo)
assert.False(t, result)
})
t.Run("database scope does not match client with nil AccessedDatabases", func(t *testing.T) {
// Create client but simulate nil AccessedDatabases
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.102",
Reserved: map[string]string{
"client_id": "db-client-3",
},
},
})
// sync.Map is empty by default, so no databases are accessed
// The test verifies that a client with no accessed databases doesn't match database scope
result := mgr.matchesScope("database:test_database", "db-client-3", clientInfo)
assert.False(t, result)
})
}
func TestPushCommandWithNilStore(t *testing.T) {
mgr := NewTelemetryManager(nil) // nil etcd client = nil command store
ctx := context.Background()
_, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test",
Payload: []byte("test"),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "command store not initialized")
}
func TestDeleteCommandWithNilStore(t *testing.T) {
mgr := NewTelemetryManager(nil) // nil etcd client = nil command store
ctx := context.Background()
_, err := mgr.DeleteCommand(ctx, &milvuspb.DeleteClientCommandRequest{
CommandId: "some-command-id",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "command store not initialized")
}
func TestGetCommandsForClientWithNilStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
req := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
},
}
// Should return nil without error when store is nil
clientID := req.ClientInfo.Reserved["client_id"]
if clientID == "" {
clientID = "test-client"
}
commands := mgr.getCommandsForClientWithID(clientID, req)
assert.Nil(t, commands)
}
func TestCleanupInactiveClients(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register a client via heartbeat
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "client-old"},
},
})
// Manually set old heartbeat time to simulate inactive client
if existing, loaded := mgr.clientMetrics.Load("client-old"); loaded {
cache := existing.(*ClientMetricsCache)
cache.LastHeartbeat.Store(time.Now().Add(-35 * time.Minute).UnixNano())
}
// Add another active client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "java-sdk",
Host: "192.168.1.101",
Reserved: map[string]string{"client_id": "client-active"},
},
})
// Verify both clients exist before cleanup
clients := mgr.ListClients("")
assert.Len(t, clients, 2)
// Run cleanup
ctx := context.Background()
mgr.cleanupInactiveClients(ctx)
// Only active client should remain
clients = mgr.ListClients("")
assert.Len(t, clients, 1)
assert.Equal(t, "client-active", clients[0].ClientID)
}
func TestStartStop(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Start should not panic
mgr.Start()
// Give the goroutine time to start
time.Sleep(10 * time.Millisecond)
// Stop should not panic and should complete
mgr.Stop()
}
func TestGetClientStatus(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Active client (recent heartbeat)
activeCache := &ClientMetricsCache{}
activeCache.LastHeartbeat.Store(time.Now().UnixNano())
assert.Equal(t, "active", mgr.getClientStatus(activeCache))
// Inactive client (old heartbeat)
inactiveCache := &ClientMetricsCache{}
inactiveCache.LastHeartbeat.Store(time.Now().Add(-10 * time.Minute).UnixNano())
assert.Equal(t, "inactive", mgr.getClientStatus(inactiveCache))
}
// Integration-style tests for full flow scenarios
func TestFullTelemetryFlow(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Simulate multiple clients sending heartbeats over time
clients := []struct {
clientID string
sdkType string
host string
}{
{"client-pymilvus-1", "pymilvus", "192.168.1.100"},
{"client-java-1", "java-sdk", "192.168.1.101"},
{"client-node-1", "node-sdk", "192.168.1.102"},
}
// Register all clients
for _, c := range clients {
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: c.sdkType,
Host: c.host,
Reserved: map[string]string{"client_id": c.clientID},
},
ReportTimestamp: time.Now().UnixMilli(),
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
SuccessCount: 95,
ErrorCount: 5,
AvgLatencyMs: 25.0,
P99LatencyMs: 100.0,
},
},
},
})
assert.NoError(t, err)
assert.NotNil(t, resp.Status)
}
// Query all clients
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 3)
assert.Equal(t, int64(300), resp.Aggregated.RequestCount) // 100 * 3 clients
assert.Equal(t, int64(285), resp.Aggregated.SuccessCount) // 95 * 3 clients
assert.Equal(t, int64(15), resp.Aggregated.ErrorCount) // 5 * 3 clients
}
func TestClientHeartbeatUpdatesExistingClient(t *testing.T) {
mgr := NewTelemetryManager(nil)
clientID := "persistent-client"
// First heartbeat
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
SdkVersion: "2.4.0",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": clientID},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 50}},
},
})
assert.NoError(t, err)
// Second heartbeat with updated metrics
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
SdkVersion: "2.4.1", // Updated version
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": clientID},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 100}}, // Updated count
},
})
assert.NoError(t, err)
// Should still only have one client
clients := mgr.ListClients("")
assert.Len(t, clients, 1)
// Metrics should be latest
assert.Equal(t, int64(100), clients[0].LatestMetrics[0].Global.RequestCount)
// SDK version should be updated
assert.Equal(t, "2.4.1", clients[0].ClientInfo.SdkVersion)
}
func TestMultipleOperationTypes(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Client reports multiple operation types
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "multi-op-client"},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 100, SuccessCount: 100}},
{Operation: "Insert", Global: &commonpb.Metrics{RequestCount: 50, SuccessCount: 48, ErrorCount: 2}},
{Operation: "Query", Global: &commonpb.Metrics{RequestCount: 30, SuccessCount: 30}},
},
})
assert.NoError(t, err)
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "multi-op-client",
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
// Verify aggregated metrics
assert.Equal(t, int64(180), resp.Aggregated.RequestCount) // 100 + 50 + 30
assert.Equal(t, int64(178), resp.Aggregated.SuccessCount) // 100 + 48 + 30
assert.Equal(t, int64(2), resp.Aggregated.ErrorCount)
}
func TestConcurrentHeartbeats(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Simulate concurrent heartbeats from multiple clients
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func(idx int) {
for j := 0; j < 100; j++ {
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": fmt.Sprintf("client-%d", idx)},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 1, SuccessCount: 1}},
},
})
assert.NoError(t, err)
}
done <- true
}(i)
}
// Wait for all goroutines
for i := 0; i < 10; i++ {
<-done
}
// Should have 10 distinct clients
clients := mgr.ListClients("")
assert.Len(t, clients, 10)
}
func TestLifecycleWithCleanup(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Start the manager
mgr.Start()
// Register some clients
for i := 0; i < 3; i++ {
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: fmt.Sprintf("192.168.1.%d", i),
Reserved: map[string]string{"client_id": fmt.Sprintf("client-%d", i)},
},
})
assert.NoError(t, err)
}
// Verify clients are registered
assert.Len(t, mgr.ListClients(""), 3)
// Stop should complete gracefully
mgr.Stop()
}
func TestGetOrCreateClientID(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Test with reserved client_id
info1 := &commonpb.ClientInfo{
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "my-custom-id"},
}
assert.Equal(t, "my-custom-id", mgr.getOrCreateClientID(info1))
// Test with nil reserved map
info2 := &commonpb.ClientInfo{
Host: "192.168.1.100",
}
id2 := mgr.getOrCreateClientID(info2)
assert.Contains(t, id2, "192.168.1.100")
// Test with nil client info
id3 := mgr.getOrCreateClientID(nil)
assert.NotEmpty(t, id3)
}
func TestGetDatabaseList(t *testing.T) {
mgr := NewTelemetryManager(nil)
cache := &ClientMetricsCache{}
cache.AccessedDatabases.Store("db1", struct{}{})
cache.AccessedDatabases.Store("db2", struct{}{})
cache.AccessedDatabases.Store("db3", struct{}{})
dbs := mgr.getDatabaseList(cache)
assert.Len(t, dbs, 3)
assert.ElementsMatch(t, []string{"db1", "db2", "db3"}, dbs)
}
func TestCleanupExpiredCommandsWithNilStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Should not panic when command store is nil
ctx := context.Background()
mgr.cleanupExpiredCommands(ctx)
}
func TestHeartbeatWithNilMetrics(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Heartbeat without metrics
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "no-metrics-client"},
},
Metrics: nil,
})
assert.NoError(t, err)
assert.NotNil(t, resp)
// Query should work
telResp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "no-metrics-client",
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, telResp.Clients, 1)
assert.Nil(t, telResp.Clients[0].Metrics)
}
func TestGetClientTelemetryWithDatabaseFilter(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "db-filter-client"},
},
})
// Manually add database access
if existing, loaded := mgr.clientMetrics.Load("db-filter-client"); loaded {
cache := existing.(*ClientMetricsCache)
cache.AccessedDatabases.Store("test_db", struct{}{})
}
// Query with matching database
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
Database: "test_db",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
// Query with non-matching database
resp, err = mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
Database: "other_db",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 0)
}
func TestListClientsWithDatabaseFilter(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register clients
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "client-with-db"},
},
})
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "java-sdk",
Host: "192.168.1.101",
Reserved: map[string]string{"client_id": "client-without-db"},
},
})
// Add database access to first client only
if existing, loaded := mgr.clientMetrics.Load("client-with-db"); loaded {
cache := existing.(*ClientMetricsCache)
cache.AccessedDatabases.Store("my_database", struct{}{})
}
// List all clients
all := mgr.ListClients("")
assert.Len(t, all, 2)
// List clients with database filter
filtered := mgr.ListClients("my_database")
assert.Len(t, filtered, 1)
assert.Equal(t, "client-with-db", filtered[0].ClientID)
// List clients with non-existent database
empty := mgr.ListClients("nonexistent")
assert.Len(t, empty, 0)
}
func TestGetClientTelemetryWithMetricsAggregation(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register multiple clients with different metrics
clients := []struct {
clientID string
requestCount int64
successCount int64
errorCount int64
}{
{"client-1", 100, 95, 5},
{"client-2", 200, 190, 10},
{"client-3", 300, 280, 20},
}
for _, c := range clients {
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": c.clientID},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: c.requestCount,
SuccessCount: c.successCount,
ErrorCount: c.errorCount,
},
},
},
})
}
// Query with metrics
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 3)
// Total: 600 requests, 565 success, 35 errors
assert.Equal(t, int64(600), resp.Aggregated.RequestCount)
assert.Equal(t, int64(565), resp.Aggregated.SuccessCount)
assert.Equal(t, int64(35), resp.Aggregated.ErrorCount)
}
func TestGetClientTelemetryWithoutMetrics(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "test-client"},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 100}},
},
})
// Query without metrics
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: false,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
// Metrics should not be included
assert.Nil(t, resp.Clients[0].Metrics)
}
func TestGetClientTelemetryAllFilters(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register clients with different databases
for i := 0; i < 3; i++ {
clientID := fmt.Sprintf("client-%d", i)
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: fmt.Sprintf("192.168.1.%d", i),
Reserved: map[string]string{"client_id": clientID},
},
Metrics: []*commonpb.OperationMetrics{
{Operation: "Search", Global: &commonpb.Metrics{RequestCount: 10}},
},
})
// Add database for first two clients
if i < 2 {
if existing, loaded := mgr.clientMetrics.Load(clientID); loaded {
cache := existing.(*ClientMetricsCache)
cache.AccessedDatabases.Store("shared_db", struct{}{})
}
}
}
// Filter by client_id
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "client-1",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
// Filter by database
resp, err = mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
Database: "shared_db",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 2)
// Filter by non-existent client
resp, err = mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "non-existent",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 0)
}
func TestHeartbeatUpdatesConfigHash(t *testing.T) {
mgr := NewTelemetryManager(nil)
// First heartbeat
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "hash-test-client"},
},
ConfigHash: "initial-hash",
LastCommandTimestamp: 12345,
})
assert.NoError(t, err)
// Verify hash and timestamp were stored
existing, loaded := mgr.clientMetrics.Load("hash-test-client")
assert.True(t, loaded)
cache := existing.(*ClientMetricsCache)
assert.Equal(t, "initial-hash", cache.ConfigHash)
assert.Equal(t, int64(12345), cache.LastCommandTS)
}
func TestHeartbeatWithEmptyClientInfo(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Heartbeat with nil ClientInfo
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: nil,
})
assert.NoError(t, err)
assert.NotNil(t, resp)
// Should still work and create an entry with generated ID
clients := mgr.ListClients("")
assert.Len(t, clients, 1)
}
func TestGetClientTelemetryReturnsStatus(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register active client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "active-client"},
},
})
// Register inactive client (manually set old heartbeat)
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "java-sdk",
Host: "192.168.1.101",
Reserved: map[string]string{"client_id": "inactive-client"},
},
})
if existing, loaded := mgr.clientMetrics.Load("inactive-client"); loaded {
cache := existing.(*ClientMetricsCache)
cache.LastHeartbeat.Store(time.Now().Add(-10 * time.Minute).UnixNano())
}
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 2)
// Check status is set correctly
for _, client := range resp.Clients {
if client.ClientInfo.Reserved["client_id"] == "active-client" {
assert.Equal(t, "active", client.Status)
} else {
assert.Equal(t, "inactive", client.Status)
}
}
}
func TestGetClientTelemetryDatabaseList(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register client and add databases
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "db-list-client"},
},
})
if existing, loaded := mgr.clientMetrics.Load("db-list-client"); loaded {
cache := existing.(*ClientMetricsCache)
cache.AccessedDatabases.Store("db1", struct{}{})
cache.AccessedDatabases.Store("db2", struct{}{})
}
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "db-list-client",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
assert.ElementsMatch(t, []string{"db1", "db2"}, resp.Clients[0].Databases)
}
func TestCleanupLoopBehavior(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "cleanup-test-client"},
},
})
// Manually call cleanup functions to test them
ctx := context.Background()
mgr.cleanupInactiveClients(ctx)
mgr.cleanupExpiredCommands(ctx)
// Client should still be there (it's recent)
clients := mgr.ListClients("")
assert.Len(t, clients, 1)
}
func TestGetOrCreateClientIDEdgeCases(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Test with empty Reserved map (not nil, but empty)
info1 := &commonpb.ClientInfo{
Host: "192.168.1.100",
Reserved: map[string]string{},
}
id1 := mgr.getOrCreateClientID(info1)
assert.Contains(t, id1, "192.168.1.100")
// Test with Reserved containing empty client_id
info2 := &commonpb.ClientInfo{
Host: "192.168.1.101",
Reserved: map[string]string{"client_id": ""},
}
id2 := mgr.getOrCreateClientID(info2)
assert.Contains(t, id2, "192.168.1.101")
// Test with no host and no reserved
info3 := &commonpb.ClientInfo{}
id3 := mgr.getOrCreateClientID(info3)
assert.NotEmpty(t, id3)
}
func TestHeartbeatResponseContainsTimestamp(t *testing.T) {
mgr := NewTelemetryManager(nil)
beforeTs := time.Now().UnixMilli()
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "ts-test-client"},
},
})
afterTs := time.Now().UnixMilli()
assert.NoError(t, err)
assert.GreaterOrEqual(t, resp.ServerTimestamp, beforeTs)
assert.LessOrEqual(t, resp.ServerTimestamp, afterTs)
}
func TestMultipleClientsWithSameHost(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Two clients from the same host but different client IDs
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "app1-client"},
},
})
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "app2-client"},
},
})
clients := mgr.ListClients("")
assert.Len(t, clients, 2)
}
func TestGetClientTelemetryWithNilGlobalMetrics(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Client with operation metrics but nil Global
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "nil-global-client"},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: nil, // nil global metrics
},
},
})
// Should not panic and should return zero aggregated metrics
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
assert.Equal(t, int64(0), resp.Aggregated.RequestCount)
}
// Tests using mock command store
func TestPushCommandWithMockStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Test successful push
resp, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "set_log_level",
Payload: []byte(`{"level":"debug"}`),
})
assert.NoError(t, err)
assert.NotEmpty(t, resp.CommandId)
assert.NotNil(t, resp.Status)
// Verify command was stored
assert.Len(t, mockStore.commands, 1)
}
func TestPushCommandWithMockStoreError(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mockStore.shouldFailPush = true
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Test push failure
_, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock push error")
}
func TestDeleteCommandWithMockStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// First push a command
resp, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test_cmd",
})
assert.NoError(t, err)
assert.Len(t, mockStore.commands, 1)
// Now delete it
delResp, err := mgr.DeleteCommand(ctx, &milvuspb.DeleteClientCommandRequest{
CommandId: resp.CommandId,
})
assert.NoError(t, err)
assert.NotNil(t, delResp.Status)
assert.Len(t, mockStore.commands, 0)
}
func TestDeleteCommandWithMockStoreError(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mockStore.shouldFailDelete = true
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Test delete failure
_, err := mgr.DeleteCommand(ctx, &milvuspb.DeleteClientCommandRequest{
CommandId: "any-id",
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "mock delete error")
}
func TestGetCommandsForClientWithMockStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push some commands (one-time)
mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "global_cmd",
})
mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "client_cmd",
TargetClientId: "specific-client",
})
// Test heartbeat returns commands
req := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "specific-client"},
},
ConfigHash: "",
LastCommandTimestamp: 0,
}
commands := mgr.getCommandsForClientWithID("specific-client", req)
// Should get both global and client-specific commands
assert.NotNil(t, commands)
}
func TestGetCommandsForClientListError(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mockStore.shouldFailList = true
mgr.SetCommandStore(mockStore)
req := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "test-client"},
},
}
// Should return nil when list fails
commands := mgr.getCommandsForClientWithID("test-client", req)
assert.Nil(t, commands)
}
func TestGetCommandsForClientWithConfigHashChange(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push a persistent config (not a one-time command)
mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Persistent: true,
})
// First heartbeat with empty hash - should get persistent config
req1 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "hash-test-client"},
},
ConfigHash: "",
LastCommandTimestamp: time.Now().UnixMilli(), // high timestamp so no one-time commands match
}
commands1 := mgr.getCommandsForClientWithID("hash-test-client", req1)
assert.NotEmpty(t, commands1)
// Compute the actual hash from received configs (same way client would)
var receivedConfigs []*ClientConfig
for _, cmd := range commands1 {
if cmd.Persistent {
receivedConfigs = append(receivedConfigs, &ClientConfig{
ConfigId: cmd.CommandId,
ConfigType: cmd.CommandType,
Payload: cmd.Payload,
})
}
}
computedHash := computeClientConfigHash(receivedConfigs)
// Second heartbeat with matching hash - should not get config again
req2 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "hash-test-client"},
},
ConfigHash: computedHash, // matches computed hash
LastCommandTimestamp: time.Now().UnixMilli(),
}
commands2 := mgr.getCommandsForClientWithID("hash-test-client", req2)
assert.Empty(t, commands2)
}
func TestGetCommandsForClientWithOneTimeCommand(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push a one-time command
mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "one_time_cmd",
})
// First heartbeat with timestamp 0 - should get the command
req1 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "onetime-client"},
},
LastCommandTimestamp: 0,
}
commands1 := mgr.getCommandsForClientWithID("onetime-client", req1)
assert.NotEmpty(t, commands1)
// Second heartbeat with recent timestamp - should not get the command
req2 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "onetime-client"},
},
LastCommandTimestamp: time.Now().UnixMilli() + 1000, // future timestamp
}
commands2 := mgr.getCommandsForClientWithID("onetime-client", req2)
assert.Empty(t, commands2)
}
func TestHeartbeatReturnsCommands(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push a persistent config (so it returns on heartbeat with empty hash)
mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Persistent: true,
})
// Heartbeat should return the config as command
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "cmd-test-client"},
},
ConfigHash: "",
LastCommandTimestamp: time.Now().UnixMilli(), // high ts so no one-time commands
})
assert.NoError(t, err)
assert.NotNil(t, resp.Commands)
assert.Len(t, resp.Commands, 1)
}
func TestCleanupExpiredCommandsWithMockStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
// Should not panic with mock store
ctx := context.Background()
mgr.cleanupExpiredCommands(ctx)
}
func TestSetCommandStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Initially nil
assert.Nil(t, mgr.commandStore)
// Set mock store
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
assert.NotNil(t, mgr.commandStore)
}
func TestPushCommandWithTargetClient(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push command with target client
resp, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "client_specific_cmd",
TargetClientId: "target-client-123",
})
assert.NoError(t, err)
// Verify scope was set correctly
cmd := mockStore.commands[resp.CommandId]
assert.Equal(t, "client:target-client-123", cmd.TargetScope)
}
func TestCommandScopeFiltering(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push configs with different scopes (persistent)
mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Persistent: true,
})
mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
TargetClientId: "client-A",
Persistent: true,
})
mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
TargetClientId: "client-B",
Persistent: true,
})
// Client A should get global + client-A specific (but not client-B specific)
reqA := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "client-A"},
},
ConfigHash: "",
LastCommandTimestamp: time.Now().UnixMilli(), // high ts so no one-time commands
}
commandsA := mgr.getCommandsForClientWithID("client-A", reqA)
// Should get global and client-A configs (not client-B)
assert.GreaterOrEqual(t, len(commandsA), 2)
}
// Tests for configurable parameters
func TestDefaultTelemetryConfig(t *testing.T) {
config := DefaultTelemetryConfig()
assert.NotNil(t, config)
assert.Equal(t, 1*time.Minute, config.CleanupInterval)
assert.Equal(t, 10*time.Minute, config.InactiveClientThreshold)
assert.Equal(t, 1*time.Minute, config.ClientStatusThreshold)
assert.Equal(t, 10*time.Second, config.CommandCleanupTimeout)
}
func TestNewTelemetryManagerWithConfig(t *testing.T) {
t.Run("with custom config", func(t *testing.T) {
customConfig := &TelemetryConfig{
CleanupInterval: 1 * time.Minute,
InactiveClientThreshold: 10 * time.Minute,
ClientStatusThreshold: 2 * time.Minute,
CommandCleanupTimeout: 5 * time.Second,
}
mgr := NewTelemetryManagerWithConfig(nil, customConfig)
assert.NotNil(t, mgr)
assert.Equal(t, customConfig, mgr.config)
})
t.Run("with nil config uses default", func(t *testing.T) {
mgr := NewTelemetryManagerWithConfig(nil, nil)
assert.NotNil(t, mgr)
assert.NotNil(t, mgr.config)
assert.Equal(t, 1*time.Minute, mgr.config.CleanupInterval)
})
}
func TestSetConfig(t *testing.T) {
mgr := NewTelemetryManager(nil)
newConfig := &TelemetryConfig{
CleanupInterval: 2 * time.Minute,
InactiveClientThreshold: 15 * time.Minute,
ClientStatusThreshold: 3 * time.Minute,
CommandCleanupTimeout: 8 * time.Second,
}
mgr.SetConfig(newConfig)
assert.Equal(t, newConfig, mgr.config)
}
func TestCleanupWithCustomThreshold(t *testing.T) {
// Create manager with short threshold for testing
customConfig := &TelemetryConfig{
CleanupInterval: 100 * time.Millisecond,
InactiveClientThreshold: 50 * time.Millisecond,
ClientStatusThreshold: 25 * time.Millisecond,
CommandCleanupTimeout: 1 * time.Second,
}
mgr := NewTelemetryManagerWithConfig(nil, customConfig)
// Register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "threshold-test-client"},
},
})
// Verify client exists
clients := mgr.ListClients("")
assert.Len(t, clients, 1)
// Wait for threshold to expire
time.Sleep(100 * time.Millisecond)
// Run cleanup
ctx := context.Background()
mgr.cleanupInactiveClients(ctx)
// Client should be removed due to short threshold
clients = mgr.ListClients("")
assert.Len(t, clients, 0)
}
func TestClientStatusWithCustomThreshold(t *testing.T) {
// Create manager with short status threshold
customConfig := &TelemetryConfig{
CleanupInterval: 5 * time.Minute,
InactiveClientThreshold: 30 * time.Minute,
ClientStatusThreshold: 50 * time.Millisecond, // Very short for testing
CommandCleanupTimeout: 10 * time.Second,
}
mgr := NewTelemetryManagerWithConfig(nil, customConfig)
// Register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "status-test-client"},
},
})
// Client should be active immediately
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "status-test-client",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
assert.Equal(t, "active", resp.Clients[0].Status)
// Wait for status threshold to expire
time.Sleep(100 * time.Millisecond)
// Client should now be inactive
resp, err = mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "status-test-client",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
assert.Equal(t, "inactive", resp.Clients[0].Status)
}
func TestCleanupLoopWithCustomInterval(t *testing.T) {
// Create manager with very short cleanup interval
customConfig := &TelemetryConfig{
CleanupInterval: 50 * time.Millisecond,
InactiveClientThreshold: 25 * time.Millisecond, // Very short
ClientStatusThreshold: 10 * time.Millisecond,
CommandCleanupTimeout: 1 * time.Second,
}
mgr := NewTelemetryManagerWithConfig(nil, customConfig)
// Register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "interval-test-client"},
},
})
// Verify client exists
clients := mgr.ListClients("")
assert.Len(t, clients, 1)
// Start the cleanup loop
mgr.Start()
// Wait for cleanup to run (at least one interval + threshold)
time.Sleep(150 * time.Millisecond)
// Stop the manager
mgr.Stop()
// Client should have been cleaned up by the background loop
clients = mgr.ListClients("")
assert.Len(t, clients, 0)
}
func TestMultipleStartStop(t *testing.T) {
customConfig := &TelemetryConfig{
CleanupInterval: 1 * time.Hour, // Long interval to avoid actual cleanup
InactiveClientThreshold: 30 * time.Minute,
ClientStatusThreshold: 5 * time.Minute,
CommandCleanupTimeout: 10 * time.Second,
}
// Test that we can create, start, and stop multiple managers
for i := 0; i < 3; i++ {
mgr := NewTelemetryManagerWithConfig(nil, customConfig)
mgr.Start()
time.Sleep(10 * time.Millisecond)
mgr.Stop()
}
}
func TestConfigFieldsUsedCorrectly(t *testing.T) {
// Test that each config field affects the correct behavior
t.Run("InactiveClientThreshold affects cleanup", func(t *testing.T) {
// Short threshold - clients get cleaned up quickly
shortConfig := &TelemetryConfig{
CleanupInterval: 5 * time.Minute,
InactiveClientThreshold: 10 * time.Millisecond,
ClientStatusThreshold: 5 * time.Minute,
CommandCleanupTimeout: 10 * time.Second,
}
mgr := NewTelemetryManagerWithConfig(nil, shortConfig)
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client"},
},
})
time.Sleep(50 * time.Millisecond)
ctx := context.Background()
mgr.cleanupInactiveClients(ctx)
assert.Len(t, mgr.ListClients(""), 0, "client should be cleaned up with short threshold")
})
t.Run("ClientStatusThreshold affects status reporting", func(t *testing.T) {
// Long threshold - clients stay active longer
longConfig := &TelemetryConfig{
CleanupInterval: 5 * time.Minute,
InactiveClientThreshold: 30 * time.Minute,
ClientStatusThreshold: 1 * time.Hour, // Very long
CommandCleanupTimeout: 10 * time.Second,
}
mgr := NewTelemetryManagerWithConfig(nil, longConfig)
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client"},
},
})
// Even after some delay, client should still be "active" with long threshold
time.Sleep(50 * time.Millisecond)
resp, _ := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "test-client",
})
assert.Equal(t, "active", resp.Clients[0].Status)
})
}
// TestProcessCommandReplies tests the processCommandReplies method
func TestProcessCommandReplies(t *testing.T) {
t.Run("successful_command_reply", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
// First send heartbeat to register client
_, _ = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client"},
},
})
// Send heartbeat with command replies
replies := []*commonpb.CommandReply{
{
CommandId: "cmd-1",
Success: true,
},
{
CommandId: "cmd-2",
Success: false,
ErrorMessage: "execution failed",
},
}
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client"},
},
CommandReplies: replies,
})
assert.NoError(t, err)
assert.NotNil(t, resp)
})
t.Run("empty_command_replies", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client-2"},
},
CommandReplies: []*commonpb.CommandReply{},
})
assert.NoError(t, err)
assert.NotNil(t, resp)
})
}
// TestSetErrorCollector tests the SetErrorCollector method
func TestSetErrorCollector(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Mock error collector
mockCollector := &mockErrorCollector{}
mgr.SetErrorCollector(mockCollector)
assert.NotNil(t, mgr.commandRouter)
}
// TestValidateAndTruncateMetricsEdgeCases tests edge cases for metrics validation
func TestValidateAndTruncateMetricsEdgeCases(t *testing.T) {
mgr := NewTelemetryManagerWithConfig(nil, &TelemetryConfig{
MaxOperationTypesPerClient: 2,
})
t.Run("truncate_many_operation_types", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "insert",
Global: &commonpb.Metrics{RequestCount: 10},
},
{
Operation: "search",
Global: &commonpb.Metrics{RequestCount: 20},
},
{
Operation: "delete",
Global: &commonpb.Metrics{RequestCount: 30},
},
}
validated := mgr.validateAndTruncateMetrics(metrics)
assert.Equal(t, 2, len(validated))
})
t.Run("filter_zero_request_metrics", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "insert",
CollectionMetrics: map[string]*commonpb.Metrics{
"col1": {RequestCount: 10, SuccessCount: 9},
"col2": {RequestCount: 0, SuccessCount: 0},
"col3": nil,
},
},
}
validated := mgr.validateAndTruncateMetrics(metrics)
assert.Equal(t, 1, len(validated[0].CollectionMetrics))
assert.Contains(t, validated[0].CollectionMetrics, "col1")
})
}
// TestHandleHeartbeatWithNilClientInfo tests heartbeat with nil client info
func TestHandleHeartbeatWithNilClientInfo(t *testing.T) {
mgr := NewTelemetryManager(nil)
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: nil,
})
assert.NoError(t, err)
assert.NotNil(t, resp)
}
// TestMemoryLimitEnforcement tests that memory limit is properly enforced
func TestMemoryLimitEnforcement(t *testing.T) {
smallConfig := &TelemetryConfig{
CleanupInterval: 10 * time.Millisecond,
InactiveClientThreshold: 100 * time.Millisecond,
MaxClientsInMemory: 5,
}
mgr := NewTelemetryManagerWithConfig(nil, smallConfig)
mgr.Start()
defer mgr.Stop()
// Add clients until we exceed limit
for i := 0; i < 10; i++ {
_, _ = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Host: fmt.Sprintf("host-%d", i),
},
})
}
// Wait for cleanup
time.Sleep(50 * time.Millisecond)
// Verify count is controlled
count := 0
mgr.clientMetrics.Range(func(key, value any) bool {
count++
return true
})
assert.Less(t, count, 20)
}
// Mock error collector for testing
type mockErrorCollector struct{}
func (m *mockErrorCollector) GetRecentErrors(maxCount int) {
}
// =============================================================================
// Additional Tests for Metrics Aggregation and Command Idempotency
// =============================================================================
// TestLatencyMetricsAggregation verifies that latency metrics (P99, Avg, Max) are
// properly stored and returned for each client. Note: Current implementation
// aggregates only count metrics (RequestCount, SuccessCount, ErrorCount) across clients,
// but latency metrics are preserved per-client in the response.
func TestLatencyMetricsAggregation(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Register clients with different latency metrics
clients := []struct {
clientID string
avgLatency float64
p99Latency float64
maxLatency float64
requestCount int64
}{
{"client-1", 10.5, 50.0, 100.0, 100},
{"client-2", 20.0, 80.0, 150.0, 200},
{"client-3", 15.0, 60.0, 120.0, 150},
}
for _, c := range clients {
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": c.clientID},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: c.requestCount,
SuccessCount: c.requestCount,
AvgLatencyMs: c.avgLatency,
P99LatencyMs: c.p99Latency,
MaxLatencyMs: c.maxLatency,
},
},
},
})
assert.NoError(t, err)
}
// Query with metrics included
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 3)
// Verify count aggregation
assert.Equal(t, int64(450), resp.Aggregated.RequestCount) // 100 + 200 + 150
assert.Equal(t, int64(450), resp.Aggregated.SuccessCount)
// Verify each client has correct latency metrics preserved
clientMetrics := make(map[string]*commonpb.Metrics)
for _, client := range resp.Clients {
clientID := client.ClientInfo.Reserved["client_id"]
if len(client.Metrics) > 0 && client.Metrics[0].Global != nil {
clientMetrics[clientID] = client.Metrics[0].Global
}
}
// Verify client-1 latency
assert.Equal(t, 10.5, clientMetrics["client-1"].AvgLatencyMs)
assert.Equal(t, 50.0, clientMetrics["client-1"].P99LatencyMs)
assert.Equal(t, 100.0, clientMetrics["client-1"].MaxLatencyMs)
// Verify client-2 latency
assert.Equal(t, 20.0, clientMetrics["client-2"].AvgLatencyMs)
assert.Equal(t, 80.0, clientMetrics["client-2"].P99LatencyMs)
assert.Equal(t, 150.0, clientMetrics["client-2"].MaxLatencyMs)
// Verify client-3 latency
assert.Equal(t, 15.0, clientMetrics["client-3"].AvgLatencyMs)
assert.Equal(t, 60.0, clientMetrics["client-3"].P99LatencyMs)
assert.Equal(t, 120.0, clientMetrics["client-3"].MaxLatencyMs)
}
// TestCollectionLevelMetricsAggregation verifies that collection-level metrics
// are properly stored and returned per client.
func TestCollectionLevelMetricsAggregation(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Client 1: metrics for collections A and B
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": "client-1"},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
SuccessCount: 95,
ErrorCount: 5,
},
CollectionMetrics: map[string]*commonpb.Metrics{
"collection_A": {
RequestCount: 60,
SuccessCount: 58,
ErrorCount: 2,
AvgLatencyMs: 10.0,
P99LatencyMs: 50.0,
MaxLatencyMs: 100.0,
},
"collection_B": {
RequestCount: 40,
SuccessCount: 37,
ErrorCount: 3,
AvgLatencyMs: 15.0,
P99LatencyMs: 70.0,
MaxLatencyMs: 150.0,
},
},
},
},
})
assert.NoError(t, err)
// Client 2: metrics for collections A and C
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "java-sdk",
Host: "192.168.1.101",
Reserved: map[string]string{"client_id": "client-2"},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 200,
SuccessCount: 190,
ErrorCount: 10,
},
CollectionMetrics: map[string]*commonpb.Metrics{
"collection_A": {
RequestCount: 120,
SuccessCount: 115,
ErrorCount: 5,
AvgLatencyMs: 12.0,
P99LatencyMs: 55.0,
MaxLatencyMs: 110.0,
},
"collection_C": {
RequestCount: 80,
SuccessCount: 75,
ErrorCount: 5,
AvgLatencyMs: 20.0,
P99LatencyMs: 90.0,
MaxLatencyMs: 200.0,
},
},
},
},
})
assert.NoError(t, err)
// Query with metrics
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 2)
// Verify global aggregation
assert.Equal(t, int64(300), resp.Aggregated.RequestCount) // 100 + 200
assert.Equal(t, int64(285), resp.Aggregated.SuccessCount) // 95 + 190
assert.Equal(t, int64(15), resp.Aggregated.ErrorCount) // 5 + 10
// Verify collection-level metrics are preserved per client (including latency metrics)
for _, client := range resp.Clients {
clientID := client.ClientInfo.Reserved["client_id"]
assert.NotEmpty(t, client.Metrics)
assert.NotNil(t, client.Metrics[0].CollectionMetrics)
switch clientID {
case "client-1":
collMetrics := client.Metrics[0].CollectionMetrics
assert.Len(t, collMetrics, 2)
assert.Contains(t, collMetrics, "collection_A")
assert.Contains(t, collMetrics, "collection_B")
// Verify collection_A metrics including latency
assert.Equal(t, int64(60), collMetrics["collection_A"].RequestCount)
assert.Equal(t, int64(58), collMetrics["collection_A"].SuccessCount)
assert.Equal(t, int64(2), collMetrics["collection_A"].ErrorCount)
assert.Equal(t, 10.0, collMetrics["collection_A"].AvgLatencyMs)
assert.Equal(t, 50.0, collMetrics["collection_A"].P99LatencyMs)
assert.Equal(t, 100.0, collMetrics["collection_A"].MaxLatencyMs)
// Verify collection_B metrics including latency
assert.Equal(t, int64(40), collMetrics["collection_B"].RequestCount)
assert.Equal(t, int64(37), collMetrics["collection_B"].SuccessCount)
assert.Equal(t, int64(3), collMetrics["collection_B"].ErrorCount)
assert.Equal(t, 15.0, collMetrics["collection_B"].AvgLatencyMs)
assert.Equal(t, 70.0, collMetrics["collection_B"].P99LatencyMs)
assert.Equal(t, 150.0, collMetrics["collection_B"].MaxLatencyMs)
case "client-2":
collMetrics := client.Metrics[0].CollectionMetrics
assert.Len(t, collMetrics, 2)
assert.Contains(t, collMetrics, "collection_A")
assert.Contains(t, collMetrics, "collection_C")
// Verify collection_A metrics including latency
assert.Equal(t, int64(120), collMetrics["collection_A"].RequestCount)
assert.Equal(t, int64(115), collMetrics["collection_A"].SuccessCount)
assert.Equal(t, int64(5), collMetrics["collection_A"].ErrorCount)
assert.Equal(t, 12.0, collMetrics["collection_A"].AvgLatencyMs)
assert.Equal(t, 55.0, collMetrics["collection_A"].P99LatencyMs)
assert.Equal(t, 110.0, collMetrics["collection_A"].MaxLatencyMs)
// Verify collection_C metrics including latency
assert.Equal(t, int64(80), collMetrics["collection_C"].RequestCount)
assert.Equal(t, int64(75), collMetrics["collection_C"].SuccessCount)
assert.Equal(t, int64(5), collMetrics["collection_C"].ErrorCount)
assert.Equal(t, 20.0, collMetrics["collection_C"].AvgLatencyMs)
assert.Equal(t, 90.0, collMetrics["collection_C"].P99LatencyMs)
assert.Equal(t, 200.0, collMetrics["collection_C"].MaxLatencyMs)
}
}
}
// TestMetricsSnapshotOverwrite verifies that metrics are overwritten (not accumulated)
// when the same client sends multiple heartbeats.
func TestMetricsSnapshotOverwrite(t *testing.T) {
mgr := NewTelemetryManager(nil)
clientID := "snapshot-test-client"
// First heartbeat with initial metrics
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": clientID},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
SuccessCount: 95,
ErrorCount: 5,
AvgLatencyMs: 10.0,
},
},
},
})
assert.NoError(t, err)
// Verify initial metrics
resp1, _ := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: clientID,
IncludeMetrics: true,
})
assert.Equal(t, int64(100), resp1.Aggregated.RequestCount)
// Second heartbeat with NEW metrics (should OVERWRITE, not accumulate)
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": clientID},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 150, // New value, not 100+150=250
SuccessCount: 145,
ErrorCount: 5,
AvgLatencyMs: 12.0, // New latency
},
},
},
})
assert.NoError(t, err)
// Verify metrics were OVERWRITTEN, not accumulated
resp2, _ := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: clientID,
IncludeMetrics: true,
})
assert.Equal(t, int64(150), resp2.Aggregated.RequestCount, "metrics should be overwritten, not accumulated")
assert.Equal(t, int64(145), resp2.Aggregated.SuccessCount)
assert.Len(t, resp2.Clients, 1)
assert.Equal(t, 12.0, resp2.Clients[0].Metrics[0].Global.AvgLatencyMs)
}
// TestCommandDeduplication verifies that:
// 1. Persistent configs are not re-sent when ConfigHash matches
// 2. One-time commands are not re-sent after LastCommandTimestamp
func TestCommandDeduplication(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
clientID := "dedup-test-client"
t.Run("persistent_config_not_resent_when_hash_matches", func(t *testing.T) {
// Push a persistent config
_, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Payload: []byte(`{"enabled": true}`),
Persistent: true,
})
assert.NoError(t, err)
// First heartbeat - empty hash, should receive config
req1 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
ConfigHash: "",
LastCommandTimestamp: time.Now().UnixMilli(),
}
commands1 := mgr.getCommandsForClientWithID(clientID, req1)
assert.NotEmpty(t, commands1, "should receive config on first heartbeat")
// Compute hash from received configs (same way client would)
var receivedConfigs []*ClientConfig
for _, cmd := range commands1 {
if cmd.Persistent {
receivedConfigs = append(receivedConfigs, &ClientConfig{
ConfigId: cmd.CommandId,
ConfigType: cmd.CommandType,
Payload: cmd.Payload,
})
}
}
computedHash := computeClientConfigHash(receivedConfigs)
// Second heartbeat - matching hash, should NOT receive config
req2 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
ConfigHash: computedHash, // use computed hash
LastCommandTimestamp: time.Now().UnixMilli(),
}
commands2 := mgr.getCommandsForClientWithID(clientID, req2)
assert.Empty(t, commands2, "should NOT receive config when hash matches")
// Third heartbeat - different hash, should receive config again
req3 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
ConfigHash: "old-hash",
LastCommandTimestamp: time.Now().UnixMilli(),
}
commands3 := mgr.getCommandsForClientWithID(clientID, req3)
assert.NotEmpty(t, commands3, "should receive config when hash doesn't match")
})
t.Run("one_time_command_not_resent_after_timestamp", func(t *testing.T) {
// Create fresh store for this test
mockStore2 := newMockCommandStore()
mgr2 := NewTelemetryManager(nil)
mgr2.SetCommandStore(mockStore2)
// Push a one-time command
_, err := mgr2.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "show_errors",
Payload: []byte(`{}`),
})
assert.NoError(t, err)
// Get the command's create time
commands, _ := mockStore2.ListCommands(ctx)
assert.Len(t, commands, 1)
cmdCreateTime := commands[0].CreateTime
// Heartbeat with old timestamp - should receive command
req1 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "client-A"},
},
LastCommandTimestamp: cmdCreateTime - 1000, // Before command was created
}
cmds1 := mgr2.getCommandsForClientWithID("client-A", req1)
assert.NotEmpty(t, cmds1, "should receive command when timestamp is old")
// Heartbeat with recent timestamp - should NOT receive command
req2 := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "client-B"},
},
LastCommandTimestamp: cmdCreateTime + 1000, // After command was created
}
cmds2 := mgr2.getCommandsForClientWithID("client-B", req2)
assert.Empty(t, cmds2, "should NOT receive command when timestamp is recent")
})
}
// TestCommandDeleteAfterAck verifies that one-time commands are deleted
// after the client acknowledges them via CommandReplies.
func TestCommandDeleteAfterAck(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
clientID := "ack-test-client"
// Push a one-time command
pushResp, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "show_errors",
Payload: []byte(`{"max_count": 50}`),
})
assert.NoError(t, err)
commandID := pushResp.CommandId
// Verify command exists
commands1, _ := mockStore.ListCommands(ctx)
assert.Len(t, commands1, 1)
assert.Equal(t, commandID, commands1[0].CommandId)
// Client sends heartbeat with ACK (CommandReplies)
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "pymilvus",
Host: "192.168.1.100",
Reserved: map[string]string{"client_id": clientID},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: commandID,
Success: true,
},
},
})
assert.NoError(t, err)
// Verify command was deleted after ACK
commands2, _ := mockStore.ListCommands(ctx)
assert.Empty(t, commands2, "one-time command should be deleted after ACK")
}
// TestCommandDeleteAfterAckFailure verifies that commands are still deleted
// even if the client reports execution failure.
func TestCommandDeleteAfterAckFailure(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
clientID := "ack-failure-client"
// Push a one-time command
pushResp, err := mgr.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "show_errors",
})
assert.NoError(t, err)
commandID := pushResp.CommandId
// Client reports command execution FAILED
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: commandID,
Success: false,
ErrorMessage: "command execution failed",
},
},
})
assert.NoError(t, err)
// Command should still be deleted (to avoid infinite retry loops)
commands, _ := mockStore.ListCommands(ctx)
assert.Empty(t, commands, "command should be deleted even after failure ACK")
}
// TestPersistentConfigNotDeletedAfterAck verifies that persistent configs
// are NOT deleted after ACK (they should persist).
func TestPersistentConfigNotDeletedAfterAck(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
clientID := "persistent-ack-client"
// Push a persistent config
_, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Payload: []byte(`{"enabled": true}`),
Persistent: true,
})
assert.NoError(t, err)
// Verify config exists
configs1, _, _ := mockStore.ListConfigs(ctx)
assert.Len(t, configs1, 1)
configID := configs1[0].ConfigId
// Client ACKs the config
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: configID,
Success: true,
},
},
})
assert.NoError(t, err)
// Persistent config should still exist
configs2, _, _ := mockStore.ListConfigs(ctx)
assert.Len(t, configs2, 1, "persistent config should NOT be deleted after ACK")
assert.Equal(t, configID, configs2[0].ConfigId)
}
// TestConcurrentCommandDelivery verifies that commands are delivered correctly
// under concurrent heartbeats from multiple clients.
func TestConcurrentCommandDelivery(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push a global persistent config
_, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Payload: []byte(`{"global": true}`),
Persistent: true,
})
assert.NoError(t, err)
// Concurrent heartbeats from multiple clients
var wg sync.WaitGroup
results := make(chan int, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
clientID := fmt.Sprintf("concurrent-client-%d", idx)
// First heartbeat - should get config
req := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
ConfigHash: "",
LastCommandTimestamp: time.Now().UnixMilli(),
}
commands := mgr.getCommandsForClientWithID(clientID, req)
results <- len(commands)
}(i)
}
wg.Wait()
close(results)
// All clients should have received the config
totalReceived := 0
for count := range results {
if count > 0 {
totalReceived++
}
}
assert.Equal(t, 10, totalReceived, "all concurrent clients should receive the config")
}
// TestGetClientCommandReplies tests the GetClientCommandReplies function
func TestGetClientCommandReplies(t *testing.T) {
t.Run("returns nil for non-existent client", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
replies := mgr.GetClientCommandReplies("non-existent-client")
assert.Nil(t, replies)
})
t.Run("returns nil for client with empty replies", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
clientID := "test-client"
// Register client via heartbeat
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
})
assert.NoError(t, err)
replies := mgr.GetClientCommandReplies(clientID)
assert.Nil(t, replies)
})
t.Run("returns copies of stored replies", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
clientID := "reply-test-client"
// Push a command
cmdID, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test_command",
Payload: []byte(`{"test": true}`),
})
assert.NoError(t, err)
// Client sends heartbeat with command reply
_, err = mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: cmdID,
Success: true,
Payload: []byte(`{"result": "ok"}`),
},
},
})
assert.NoError(t, err)
// Get replies - should return stored replies
replies := mgr.GetClientCommandReplies(clientID)
assert.NotNil(t, replies)
assert.Len(t, replies, 1)
assert.Equal(t, cmdID, replies[0].CommandID)
assert.True(t, replies[0].Success)
})
}
// TestListAllCommands tests the ListAllCommands function
func TestListAllCommands(t *testing.T) {
t.Run("returns nil when commandStore is nil", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mgr.SetCommandStore(nil)
commands, err := mgr.ListAllCommands(context.Background())
assert.NoError(t, err)
assert.Nil(t, commands)
})
t.Run("returns all commands from store", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push multiple commands
cmdID1, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "command_type_1",
Payload: []byte(`{"cmd": 1}`),
})
assert.NoError(t, err)
cmdID2, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Payload: []byte(`{"enabled": true}`),
Persistent: true,
TargetClientId: "specific-client",
})
assert.NoError(t, err)
// List all commands
commands, err := mgr.ListAllCommands(ctx)
assert.NoError(t, err)
assert.Len(t, commands, 2)
// Verify command info
cmdMap := make(map[string]*CommandInfo)
for _, cmd := range commands {
cmdMap[cmd.CommandID] = cmd
}
assert.Equal(t, "command_type_1", cmdMap[cmdID1].CommandType)
assert.Equal(t, "push_config", cmdMap[cmdID2].CommandType)
assert.True(t, cmdMap[cmdID2].Persistent)
})
t.Run("handles ListCommandsWithInfo error", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mockStore.shouldFailList = true
mgr.SetCommandStore(mockStore)
_, err := mgr.ListAllCommands(context.Background())
assert.Error(t, err)
})
}
// TestCloneClientInfo tests the cloneClientInfo function
func TestCloneClientInfo(t *testing.T) {
t.Run("returns nil for nil input", func(t *testing.T) {
result := cloneClientInfo(nil)
assert.Nil(t, result)
})
t.Run("clones client info correctly", func(t *testing.T) {
original := &commonpb.ClientInfo{
SdkType: "GoMilvusClient",
SdkVersion: "2.0.0",
User: "testuser",
Host: "localhost",
Reserved: map[string]string{
"client_id": "test-123",
"db_name": "test_db",
},
}
clone := cloneClientInfo(original)
assert.NotNil(t, clone)
assert.Equal(t, original.SdkType, clone.SdkType)
assert.Equal(t, original.SdkVersion, clone.SdkVersion)
assert.Equal(t, original.User, clone.User)
assert.Equal(t, original.Reserved["client_id"], clone.Reserved["client_id"])
// Verify it's a deep copy
clone.User = "modified"
assert.NotEqual(t, original.User, clone.User)
})
}
// TestCloneOperationMetrics tests the cloneOperationMetrics function
func TestCloneOperationMetrics(t *testing.T) {
t.Run("returns nil for empty input", func(t *testing.T) {
result := cloneOperationMetrics(nil)
assert.Nil(t, result)
result = cloneOperationMetrics([]*commonpb.OperationMetrics{})
assert.Nil(t, result)
})
t.Run("handles nil metrics in slice", func(t *testing.T) {
input := []*commonpb.OperationMetrics{
nil,
{Operation: "Search"},
nil,
}
result := cloneOperationMetrics(input)
assert.Len(t, result, 3)
assert.Nil(t, result[0])
assert.NotNil(t, result[1])
assert.Nil(t, result[2])
})
t.Run("clones metrics correctly", func(t *testing.T) {
original := []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
SuccessCount: 99,
ErrorCount: 1,
AvgLatencyMs: 5.5,
P99LatencyMs: 15.0,
},
},
{
Operation: "Insert",
Global: &commonpb.Metrics{
RequestCount: 50,
SuccessCount: 50,
},
},
}
clone := cloneOperationMetrics(original)
assert.Len(t, clone, 2)
assert.Equal(t, "Search", clone[0].Operation)
assert.Equal(t, int64(100), clone[0].Global.RequestCount)
// Verify deep copy
clone[0].Global.RequestCount = 999
assert.NotEqual(t, original[0].Global.RequestCount, clone[0].Global.RequestCount)
})
}
// TestValidateAndTruncateMetricsEdgeCases2 tests additional edge cases
func TestValidateAndTruncateMetricsEdgeCases2(t *testing.T) {
t.Run("truncates to max operation types", func(t *testing.T) {
config := DefaultTelemetryConfig()
config.MaxOperationTypesPerClient = 2
mgr := NewTelemetryManagerWithConfig(nil, config)
metrics := []*commonpb.OperationMetrics{
{Operation: "Search"},
{Operation: "Insert"},
{Operation: "Delete"},
{Operation: "Query"},
}
result := mgr.validateAndTruncateMetrics(metrics)
assert.Len(t, result, 2)
})
t.Run("filters out collections with zero requests", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
metrics := []*commonpb.OperationMetrics{
{
Operation: "Search",
CollectionMetrics: map[string]*commonpb.Metrics{
"col_with_requests": {RequestCount: 10},
"col_without_requests": {RequestCount: 0},
"nil_col": nil,
},
},
}
result := mgr.validateAndTruncateMetrics(metrics)
assert.Len(t, result[0].CollectionMetrics, 1)
assert.NotNil(t, result[0].CollectionMetrics["col_with_requests"])
})
t.Run("drops collection metrics when payload too large", func(t *testing.T) {
config := DefaultTelemetryConfig()
config.MaxMetricsPerClient = 100 // Very small limit
mgr := NewTelemetryManagerWithConfig(nil, config)
metrics := []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 1000,
AvgLatencyMs: 5.5,
},
CollectionMetrics: map[string]*commonpb.Metrics{
"col1": {RequestCount: 500},
"col2": {RequestCount: 500},
},
},
}
result := mgr.validateAndTruncateMetrics(metrics)
// Collection metrics should be dropped due to size limit
assert.NotNil(t, result)
})
t.Run("handles very large operation count", func(t *testing.T) {
config := DefaultTelemetryConfig()
config.MaxMetricsPerClient = 500 // Small limit
mgr := NewTelemetryManagerWithConfig(nil, config)
// Create many operations
var metrics []*commonpb.OperationMetrics
for i := 0; i < 100; i++ {
metrics = append(metrics, &commonpb.OperationMetrics{
Operation: fmt.Sprintf("Operation%d", i),
Global: &commonpb.Metrics{
RequestCount: int64(i * 10),
AvgLatencyMs: float64(i),
},
})
}
result := mgr.validateAndTruncateMetrics(metrics)
// Should be truncated
assert.LessOrEqual(t, len(result), config.MaxOperationTypesPerClient)
})
}
// TestGetDatabaseFromClientInfo tests edge cases
func TestGetDatabaseFromClientInfoEdgeCases(t *testing.T) {
mgr := NewTelemetryManager(nil)
t.Run("returns empty for nil info", func(t *testing.T) {
db := mgr.getDatabaseFromClientInfo(nil)
assert.Empty(t, db)
})
t.Run("returns empty for nil reserved map", func(t *testing.T) {
db := mgr.getDatabaseFromClientInfo(&commonpb.ClientInfo{
Reserved: nil,
})
assert.Empty(t, db)
})
t.Run("returns empty for missing db_name key", func(t *testing.T) {
db := mgr.getDatabaseFromClientInfo(&commonpb.ClientInfo{
Reserved: map[string]string{
"other_key": "value",
},
})
assert.Empty(t, db)
})
t.Run("returns db_name when present", func(t *testing.T) {
db := mgr.getDatabaseFromClientInfo(&commonpb.ClientInfo{
Reserved: map[string]string{
"db_name": "test_database",
},
})
assert.Equal(t, "test_database", db)
})
}
// TestEstimateMetricsSize tests the estimateMetricsSize function
func TestEstimateMetricsSize(t *testing.T) {
mgr := NewTelemetryManager(nil)
t.Run("returns 0 for empty metrics", func(t *testing.T) {
size := mgr.estimateMetricsSize(nil)
assert.Equal(t, 0, size)
size = mgr.estimateMetricsSize([]*commonpb.OperationMetrics{})
assert.Equal(t, 0, size)
})
t.Run("handles nil metrics in slice", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
nil,
{Operation: "Search"},
nil,
}
size := mgr.estimateMetricsSize(metrics)
assert.True(t, size > 0)
})
t.Run("calculates correct size", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
AvgLatencyMs: 5.5,
},
},
}
size := mgr.estimateMetricsSize(metrics)
assert.True(t, size > 0)
})
}
// TestProcessCommandRepliesEdgeCases tests edge cases in processCommandReplies
func TestProcessCommandRepliesEdgeCases(t *testing.T) {
t.Run("handles empty replies", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
cache := &ClientMetricsCache{ClientID: "test-client"}
// Should not panic with empty replies
mgr.processCommandReplies(cache, nil)
mgr.processCommandReplies(cache, []*commonpb.CommandReply{})
})
t.Run("handles unknown command ID", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
// First register client
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client"},
},
})
assert.NoError(t, err)
// Get client cache
existing, _ := mgr.clientMetrics.Load("test-client")
cache := existing.(*ClientMetricsCache)
// Process reply for unknown command
mgr.processCommandReplies(cache, []*commonpb.CommandReply{
{CommandId: "unknown-cmd-id", Success: true},
})
})
t.Run("handles failed reply", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push a command
cmdID, _ := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test_command",
})
// First register client
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": "test-client"},
},
})
assert.NoError(t, err)
// Get client cache
existing, _ := mgr.clientMetrics.Load("test-client")
cache := existing.(*ClientMetricsCache)
// Process failed reply
mgr.processCommandReplies(cache, []*commonpb.CommandReply{
{
CommandId: cmdID,
Success: false,
ErrorMessage: "command failed",
},
})
})
}
// TestLookupCommandInfo tests the lookupCommandInfo function
func TestLookupCommandInfo(t *testing.T) {
t.Run("returns empty when store is nil", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mgr.SetCommandStore(nil)
cmdType, payload := mgr.lookupCommandInfo("any-id")
assert.Empty(t, cmdType)
assert.Nil(t, payload)
})
t.Run("returns empty for non-existent command", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
cmdType, payload := mgr.lookupCommandInfo("non-existent")
assert.Empty(t, cmdType)
assert.Nil(t, payload)
})
t.Run("returns command info for existing command", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Use non-persistent command type
cmdID, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test_type",
Payload: []byte(`{"test": true}`),
Persistent: false,
})
assert.NoError(t, err)
cmdType, payload := mgr.lookupCommandInfo(cmdID)
assert.Equal(t, "test_type", cmdType)
assert.NotEmpty(t, payload)
})
t.Run("returns command info for persistent config", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Use push_config for persistent commands
cmdID, err := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Payload: []byte(`{"config": "value"}`),
Persistent: true,
})
assert.NoError(t, err)
cmdType, payload := mgr.lookupCommandInfo(cmdID)
assert.Equal(t, "push_config", cmdType)
assert.NotEmpty(t, payload)
})
t.Run("returns empty for empty command ID", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
cmdType, payload := mgr.lookupCommandInfo("")
assert.Empty(t, cmdType)
assert.Nil(t, payload)
})
}
// TestCleanupRepliedCommands tests the cleanupRepliedCommands function
func TestCleanupRepliedCommands(t *testing.T) {
t.Run("no-op when store is nil", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mgr.SetCommandStore(nil)
// Should not panic
mgr.cleanupRepliedCommands([]string{"test-id"})
})
t.Run("does not delete persistent commands", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
cmdID, _ := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "push_config",
Payload: []byte(`{}`),
Persistent: true,
})
// Cleanup should not delete persistent command (DeleteNonPersistentCommand skips persistent)
mgr.cleanupRepliedCommands([]string{cmdID})
// Command should still exist
configs, _, _ := mockStore.ListConfigs(ctx)
assert.Len(t, configs, 1)
})
t.Run("deletes non-persistent commands", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
cmdID, _ := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "one_time_command",
Payload: []byte(`{}`),
Persistent: false,
})
// Cleanup should delete non-persistent command
mgr.cleanupRepliedCommands([]string{cmdID})
// Command should be deleted
commands, _ := mockStore.ListCommands(ctx)
assert.Empty(t, commands)
})
}
// TestGetClientTelemetryAggregatedMetrics tests aggregated metrics in GetClientTelemetry
func TestGetClientTelemetryAggregatedMetrics(t *testing.T) {
t.Run("aggregates metrics from multiple clients", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Create two clients with metrics
for i, cid := range []string{"agg-client-1", "agg-client-2"} {
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": cid,
},
},
Metrics: []*commonpb.OperationMetrics{
{
Operation: "search",
Global: &commonpb.Metrics{
RequestCount: int64(100 * (i + 1)),
SuccessCount: int64(90 * (i + 1)),
ErrorCount: int64(10 * (i + 1)),
},
},
},
})
}
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
IncludeMetrics: true,
})
assert.NoError(t, err)
// Aggregated should be sum: 100+200=300 requests, 90+180=270 success, 10+20=30 errors
assert.Equal(t, int64(300), resp.Aggregated.RequestCount)
assert.Equal(t, int64(270), resp.Aggregated.SuccessCount)
assert.Equal(t, int64(30), resp.Aggregated.ErrorCount)
})
}
// TestGetClientTelemetryCommandRepliesInResponse tests command replies in GetClientTelemetry
func TestGetClientTelemetryCommandRepliesInResponse(t *testing.T) {
t.Run("includes command replies in response", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
ctx := context.Background()
// Push a command
cmdResp, _ := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test_cmd",
Payload: []byte(`{"test": true}`),
})
// Client sends heartbeat with command reply
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "reply-client-2",
},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: cmdResp,
Success: true,
Payload: []byte(`{"result": "done"}`),
ErrorMessage: "",
},
},
})
// Get telemetry - should include command replies
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{
ClientId: "reply-client-2",
})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
// Check command_replies is in Reserved
assert.Contains(t, resp.Clients[0].ClientInfo.Reserved, "command_replies")
})
}
// TestProcessCommandRepliesFailedCommand tests failed command reply handling
func TestProcessCommandRepliesFailedCommand(t *testing.T) {
t.Run("logs warning for failed command", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
// Register a client first
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "failing-client-2",
},
},
})
// Send heartbeat with failed command reply
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "failing-client-2",
},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: "cmd-123",
Success: false,
ErrorMessage: "command execution failed: permission denied",
},
},
})
// Verify the reply was stored
replies := mgr.GetClientCommandReplies("failing-client-2")
assert.Len(t, replies, 1)
assert.False(t, replies[0].Success)
assert.Equal(t, "command execution failed: permission denied", replies[0].ErrorMsg)
})
}
// TestProcessCommandRepliesTruncation tests that command replies are truncated to 50
func TestProcessCommandRepliesTruncation(t *testing.T) {
t.Run("truncates replies to 50", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
// Register client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "truncate-client",
},
},
})
// Send 60 command replies (more than 50)
replies := make([]*commonpb.CommandReply, 60)
for i := 0; i < 60; i++ {
replies[i] = &commonpb.CommandReply{
CommandId: fmt.Sprintf("cmd-%d", i),
Success: true,
}
}
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "truncate-client",
},
},
CommandReplies: replies,
})
// Verify only 50 are stored (the most recent ones)
storedReplies := mgr.GetClientCommandReplies("truncate-client")
assert.Len(t, storedReplies, 50)
// The first 10 (cmd-0 to cmd-9) should be dropped, keeping cmd-10 to cmd-59
assert.Equal(t, "cmd-10", storedReplies[0].CommandID)
assert.Equal(t, "cmd-59", storedReplies[49].CommandID)
})
}
// TestGetClientTelemetryNilClientInfo tests edge cases with nil ClientInfo
func TestGetClientTelemetryNilClientInfo(t *testing.T) {
t.Run("handles nil ClientInfo in cache", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
// Directly add a client with nil ClientInfo to test the defensive code
nilCache := &ClientMetricsCache{
ClientInfo: nil,
}
nilCache.LastHeartbeat.Store(time.Now().UnixNano())
mgr.clientMetrics.Store("test-nil-client", nilCache)
resp, err := mgr.GetClientTelemetry(&milvuspb.GetClientTelemetryRequest{})
assert.NoError(t, err)
assert.Len(t, resp.Clients, 1)
// Defensive code should create ClientInfo
assert.NotNil(t, resp.Clients[0].ClientInfo)
assert.NotNil(t, resp.Clients[0].ClientInfo.Reserved)
assert.Equal(t, "test-nil-client", resp.Clients[0].ClientInfo.Reserved["client_id"])
})
}
// TestCloneOperationMetricsNilBranches tests nil handling in cloneOperationMetrics
func TestCloneOperationMetricsNilBranches(t *testing.T) {
t.Run("returns nil for nil input", func(t *testing.T) {
result := cloneOperationMetrics(nil)
assert.Nil(t, result)
})
t.Run("returns nil for empty slice", func(t *testing.T) {
result := cloneOperationMetrics([]*commonpb.OperationMetrics{})
assert.Nil(t, result)
})
t.Run("handles nil element in slice", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "search",
Global: &commonpb.Metrics{RequestCount: 100},
},
nil, // nil element
{
Operation: "insert",
Global: &commonpb.Metrics{RequestCount: 50},
},
}
result := cloneOperationMetrics(metrics)
assert.NotNil(t, result)
assert.Len(t, result, 3)
assert.Nil(t, result[1]) // nil should be preserved
})
t.Run("handles operations with nil Global", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "search",
Global: nil,
CollectionMetrics: map[string]*commonpb.Metrics{
"col1": {RequestCount: 10},
},
},
}
result := cloneOperationMetrics(metrics)
assert.NotNil(t, result)
assert.Len(t, result, 1)
assert.Nil(t, result[0].Global)
assert.NotNil(t, result[0].CollectionMetrics)
})
t.Run("handles operations with nil CollectionMetrics", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "insert",
Global: &commonpb.Metrics{
RequestCount: 100,
},
CollectionMetrics: nil,
},
}
result := cloneOperationMetrics(metrics)
assert.NotNil(t, result)
assert.Len(t, result, 1)
assert.NotNil(t, result[0].Global)
assert.Nil(t, result[0].CollectionMetrics)
})
}
// TestCloneClientInfoNilBranches tests nil handling in cloneClientInfo
func TestCloneClientInfoNilBranches(t *testing.T) {
t.Run("returns nil for nil input", func(t *testing.T) {
result := cloneClientInfo(nil)
assert.Nil(t, result)
})
t.Run("handles ClientInfo with nil Reserved", func(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test",
SdkVersion: "1.0",
Reserved: nil,
}
result := cloneClientInfo(info)
assert.NotNil(t, result)
assert.Equal(t, "test", result.SdkType)
assert.Nil(t, result.Reserved)
})
t.Run("clones Reserved map", func(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test",
Reserved: map[string]string{
"key1": "value1",
"key2": "value2",
},
}
result := cloneClientInfo(info)
assert.NotNil(t, result)
assert.NotNil(t, result.Reserved)
assert.Equal(t, "value1", result.Reserved["key1"])
// Verify it's a deep copy
result.Reserved["key1"] = "modified"
assert.Equal(t, "value1", info.Reserved["key1"])
})
}
// TestGetDatabaseFromClientInfoEdgeCases2 tests more edge cases in getDatabaseFromClientInfo
func TestGetDatabaseFromClientInfoEdgeCases2(t *testing.T) {
mgr := NewTelemetryManager(nil)
t.Run("returns empty for nil info", func(t *testing.T) {
db := mgr.getDatabaseFromClientInfo(nil)
assert.Equal(t, "", db)
})
t.Run("returns empty for nil reserved", func(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test",
Reserved: nil,
}
db := mgr.getDatabaseFromClientInfo(info)
assert.Equal(t, "", db)
})
t.Run("returns empty for empty db_name", func(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test",
Reserved: map[string]string{
"db_name": "",
},
}
db := mgr.getDatabaseFromClientInfo(info)
assert.Equal(t, "", db)
})
t.Run("returns database from database key", func(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test",
Reserved: map[string]string{
"database": "alt_database",
},
}
db := mgr.getDatabaseFromClientInfo(info)
assert.Equal(t, "alt_database", db)
})
t.Run("trims whitespace from db_name", func(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test",
Reserved: map[string]string{
"db_name": " trimmed_db ",
},
}
db := mgr.getDatabaseFromClientInfo(info)
assert.Equal(t, "trimmed_db", db)
})
}
// TestProcessCommandRepliesWithNilReply tests handling of nil replies in the slice
func TestProcessCommandRepliesWithNilReply(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
// Register client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "nil-reply-client",
},
},
})
// Send heartbeat with nil reply in the slice
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "nil-reply-client",
},
},
CommandReplies: []*commonpb.CommandReply{
{
CommandId: "cmd-1",
Success: true,
},
nil, // nil reply should be skipped
{
CommandId: "cmd-2",
Success: true,
},
},
})
// Verify only non-nil replies were stored
replies := mgr.GetClientCommandReplies("nil-reply-client")
assert.Len(t, replies, 2)
}
// TestGetCommandsForClientWithIDErrorPaths tests error handling in getCommandsForClientWithID
func TestGetCommandsForClientWithIDErrorPaths(t *testing.T) {
t.Run("handles ListCommands error", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mockStore.shouldFailList = true
mgr.SetCommandStore(mockStore)
// First register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "error-test-client",
},
},
})
// Heartbeat should still succeed but return no commands
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "error-test-client",
},
},
})
assert.NoError(t, err)
assert.Empty(t, resp.Commands)
})
t.Run("handles ListConfigs error", func(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mockStore.shouldFailListConfig = true
mgr.SetCommandStore(mockStore)
// First register a client
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "error-test-client-2",
},
},
})
// Heartbeat should still succeed but return no commands
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "error-test-client-2",
},
},
})
assert.NoError(t, err)
assert.Empty(t, resp.Commands)
})
}
// TestHandleHeartbeatAccessedDatabasesEmpty tests that adding to empty AccessedDatabases works
func TestHandleHeartbeatAccessedDatabasesEmpty(t *testing.T) {
mgr := NewTelemetryManager(nil)
// First, register a client (AccessedDatabases starts as empty sync.Map)
mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "access-db-test",
},
},
})
// Now send heartbeat with db_name - should work on empty sync.Map
resp, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
Reserved: map[string]string{
"client_id": "access-db-test",
"db_name": "new_database",
},
},
})
assert.NoError(t, err)
assert.NotNil(t, resp)
// Verify database was added
existing, loaded := mgr.clientMetrics.Load("access-db-test")
assert.True(t, loaded)
cache := existing.(*ClientMetricsCache)
_, exists := cache.AccessedDatabases.Load("new_database")
assert.True(t, exists)
}
// TestNewTelemetryManagerWithConfigNilConfig tests nil config handling
func TestNewTelemetryManagerWithConfigNilConfig(t *testing.T) {
// Test with nil config - should use default
mgr := NewTelemetryManagerWithConfig(nil, nil)
assert.NotNil(t, mgr)
assert.NotNil(t, mgr.config)
assert.Equal(t, DefaultTelemetryConfig().ClientStatusThreshold, mgr.config.ClientStatusThreshold)
}
// TestCleanupRepliedCommandsNilCommandStore tests cleanup when commandStore is nil
func TestCleanupRepliedCommandsNilCommandStore(t *testing.T) {
mgr := NewTelemetryManager(nil)
// commandStore is nil by default
// Should return early without panic
mgr.cleanupRepliedCommands([]string{"cmd1", "cmd2"})
}
// TestCleanupRepliedCommandsEmptyIDs tests cleanup with empty string IDs
func TestCleanupRepliedCommandsEmptyIDs(t *testing.T) {
mockStore := newMockCommandStore()
mgr := NewTelemetryManager(nil)
mgr.SetCommandStore(mockStore)
// Add a command using the mock store directly
ctx := context.Background()
cmdID, _ := mockStore.PushCommand(ctx, &milvuspb.PushClientCommandRequest{
CommandType: "test_command",
})
// Cleanup with empty strings and valid ID - should skip empty strings
mgr.cleanupRepliedCommands([]string{"", cmdID, ""})
// Valid command should be deleted, empty strings skipped
_, _, _, found := mockStore.GetCommandInfo(cmdID)
assert.False(t, found)
}
// TestCloneOperationMetricsWithNilElement tests cloning metrics slice with nil element
func TestCloneOperationMetricsWithNilElement(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
},
},
nil, // nil element in middle
{
Operation: "Insert",
Global: &commonpb.Metrics{
RequestCount: 50,
},
},
}
cloned := cloneOperationMetrics(metrics)
assert.Len(t, cloned, 3)
assert.NotNil(t, cloned[0])
assert.Equal(t, "Search", cloned[0].Operation)
assert.Nil(t, cloned[1]) // nil element preserved
assert.NotNil(t, cloned[2])
assert.Equal(t, "Insert", cloned[2].Operation)
}
// TestCloneClientInfoNil tests cloneClientInfo with nil input
func TestCloneClientInfoNil(t *testing.T) {
result := cloneClientInfo(nil)
assert.Nil(t, result)
}
// TestCloneClientInfoValid tests cloneClientInfo with valid input
func TestCloneClientInfoValid(t *testing.T) {
info := &commonpb.ClientInfo{
SdkType: "test-sdk",
SdkVersion: "1.0.0",
Host: "localhost",
User: "testuser",
Reserved: map[string]string{
"client_id": "test-client-id",
},
}
cloned := cloneClientInfo(info)
assert.NotNil(t, cloned)
assert.Equal(t, info.SdkType, cloned.SdkType)
assert.Equal(t, info.SdkVersion, cloned.SdkVersion)
assert.Equal(t, info.Host, cloned.Host)
assert.Equal(t, info.User, cloned.User)
assert.Equal(t, info.Reserved["client_id"], cloned.Reserved["client_id"])
// Ensure it's a deep copy
cloned.Host = "modified"
assert.NotEqual(t, info.Host, cloned.Host)
}
// TestCloneOperationMetricsEmpty tests cloning empty metrics slice
func TestCloneOperationMetricsEmpty(t *testing.T) {
result := cloneOperationMetrics([]*commonpb.OperationMetrics{})
assert.Nil(t, result)
result = cloneOperationMetrics(nil)
assert.Nil(t, result)
}
// TestValidateAndTruncateMetricsEdgeCases3 tests additional edge cases for validation
func TestValidateAndTruncateMetricsEdgeCases3(t *testing.T) {
mgr := NewTelemetryManager(nil)
t.Run("nil metrics slice", func(t *testing.T) {
result := mgr.validateAndTruncateMetrics(nil)
assert.Nil(t, result)
})
t.Run("metrics with nil Global", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: nil, // nil Global
},
}
result := mgr.validateAndTruncateMetrics(metrics)
assert.Len(t, result, 1)
})
t.Run("metrics with nil CollectionMetrics entry", func(t *testing.T) {
metrics := []*commonpb.OperationMetrics{
{
Operation: "Search",
Global: &commonpb.Metrics{
RequestCount: 100,
},
CollectionMetrics: map[string]*commonpb.Metrics{
"col1": nil, // nil entry
"col2": {RequestCount: 50},
},
},
}
result := mgr.validateAndTruncateMetrics(metrics)
assert.NotNil(t, result)
})
}
// TestGetCommandsForClientWithIDNilStore2 tests getting commands when store is nil
func TestGetCommandsForClientWithIDNilStore2(t *testing.T) {
mgr := NewTelemetryManager(nil)
// commandStore is nil
req := &milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
SdkType: "test-sdk",
},
}
commands := mgr.getCommandsForClientWithID("test-client", req)
assert.Empty(t, commands)
}
// TestProcessCommandRepliesNilStore2 tests processing replies when store is nil
func TestProcessCommandRepliesNilStore2(t *testing.T) {
mgr := NewTelemetryManager(nil)
// commandStore is nil
cache := &ClientMetricsCache{ClientID: "test-client"}
replies := []*commonpb.CommandReply{
{
CommandId: "cmd1",
Success: true,
},
}
// Should not panic
mgr.processCommandReplies(cache, replies)
}
// TestStoredCommandReplyJSONSerialization tests that StoredCommandReply serializes
// Payload as a string (not base64 encoded []byte) for proper JSON API responses
func TestStoredCommandReplyJSONSerialization(t *testing.T) {
t.Run("payload should be string in JSON", func(t *testing.T) {
reply := &StoredCommandReply{
CommandID: "cmd-123",
CommandType: "get_config",
CommandPayload: `{"setting": "value"}`,
Success: true,
Payload: `{"user_config": {"address": "localhost"}, "pushed_config": {}}`,
ReceivedAt: 1234567890,
}
// Serialize to JSON
data, err := json.Marshal(reply)
assert.NoError(t, err)
// The payload should NOT be base64 encoded
// It should appear as a raw JSON string in the output
jsonStr := string(data)
assert.Contains(t, jsonStr, `"payload":"{\"user_config\"`)
assert.NotContains(t, jsonStr, "eyJ1c2VyX2NvbmZpZyI") // base64 prefix for {"user_config
// Verify it can be deserialized back
var deserialized StoredCommandReply
err = json.Unmarshal(data, &deserialized)
assert.NoError(t, err)
assert.Equal(t, reply.Payload, deserialized.Payload)
assert.Equal(t, reply.CommandPayload, deserialized.CommandPayload)
})
t.Run("payload in command_replies reserved field should be parseable by frontend", func(t *testing.T) {
// Simulate what the frontend receives
replies := []*StoredCommandReply{
{
CommandID: "cmd-123",
CommandType: "get_config",
Success: true,
Payload: `{"user_config": {"telemetry_enabled": true}, "pushed_config": {}}`,
ReceivedAt: 1234567890,
},
}
// Serialize to JSON (this is what goes into Reserved["command_replies"])
data, err := json.Marshal(replies)
assert.NoError(t, err)
// Deserialize to verify structure
var parsed []map[string]interface{}
err = json.Unmarshal(data, &parsed)
assert.NoError(t, err)
assert.Len(t, parsed, 1)
// The payload should be a string that can be JSON-parsed
payloadStr, ok := parsed[0]["payload"].(string)
assert.True(t, ok, "payload should be a string")
// Parse the payload string as JSON
var payloadData map[string]interface{}
err = json.Unmarshal([]byte(payloadStr), &payloadData)
assert.NoError(t, err)
assert.Contains(t, payloadData, "user_config")
assert.Contains(t, payloadData, "pushed_config")
})
}
// TestProcessCommandRepliesPayloadFormat tests that command reply payloads are stored as strings
func TestProcessCommandRepliesPayloadFormat(t *testing.T) {
mgr := NewTelemetryManager(nil)
mockStore := newMockCommandStore()
mgr.SetCommandStore(mockStore)
clientID := "test-client"
// Register client
_, err := mgr.HandleHeartbeat(&milvuspb.ClientHeartbeatRequest{
ClientInfo: &commonpb.ClientInfo{
Reserved: map[string]string{"client_id": clientID},
},
})
assert.NoError(t, err)
// Get client cache
existing, _ := mgr.clientMetrics.Load(clientID)
cache := existing.(*ClientMetricsCache)
// Process reply with JSON payload
jsonPayload := `{"user_config": {"address": "localhost:19530"}, "pushed_config": {"sampling_rate": 0.5}}`
mgr.processCommandReplies(cache, []*commonpb.CommandReply{
{
CommandId: "cmd-get-config",
Success: true,
Payload: []byte(jsonPayload),
},
})
// Get stored replies and verify format
replies := mgr.GetClientCommandReplies(clientID)
assert.Len(t, replies, 1)
assert.Equal(t, "cmd-get-config", replies[0].CommandID)
assert.Equal(t, jsonPayload, replies[0].Payload) // Should be string, not []byte
// Verify JSON serialization produces proper format
data, err := json.Marshal(replies)
assert.NoError(t, err)
// Should not contain base64 encoded content
jsonStr := string(data)
assert.Contains(t, jsonStr, `"payload":"{\"user_config\"`)
assert.NotContains(t, jsonStr, "eyJ1c2VyX2NvbmZpZyI") // base64 prefix
}