You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

509 lines
13 KiB

package task
import (
"fmt"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/worker/types"
)
func TestTaskAssignment_BasicAssignment(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
// Register worker
worker := &types.Worker{
ID: "worker1",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
MaxConcurrent: 1,
Status: "active",
CurrentLoad: 0,
}
registry.RegisterWorker(worker)
// Create task
task := &types.Task{
ID: "task1",
Type: types.TaskTypeVacuum,
Priority: types.TaskPriorityNormal,
}
queue.Push(task)
// Test assignment
nextTask := scheduler.GetNextTask("worker1", []types.TaskType{types.TaskTypeVacuum})
if nextTask == nil {
t.Fatal("Expected task to be assigned")
}
if nextTask.ID != "task1" {
t.Errorf("Expected task1, got %s", nextTask.ID)
}
t.Log("✅ Basic task assignment test passed")
}
func TestTaskAssignment_CapabilityMatching(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
// Register workers with different capabilities
ecWorker := &types.Worker{
ID: "ec_worker",
Capabilities: []types.TaskType{types.TaskTypeErasureCoding},
Status: "active",
CurrentLoad: 0,
}
registry.RegisterWorker(ecWorker)
vacuumWorker := &types.Worker{
ID: "vacuum_worker",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
CurrentLoad: 0,
}
registry.RegisterWorker(vacuumWorker)
// Create different types of tasks
ecTask := &types.Task{
ID: "ec_task",
Type: types.TaskTypeErasureCoding,
}
vacuumTask := &types.Task{
ID: "vacuum_task",
Type: types.TaskTypeVacuum,
}
queue.Push(ecTask)
queue.Push(vacuumTask)
// Test EC worker gets EC task
assignedECTask := scheduler.GetNextTask("ec_worker", []types.TaskType{types.TaskTypeErasureCoding})
if assignedECTask == nil || assignedECTask.Type != types.TaskTypeErasureCoding {
t.Error("EC worker should get EC task")
}
// Test vacuum worker gets vacuum task
assignedVacuumTask := scheduler.GetNextTask("vacuum_worker", []types.TaskType{types.TaskTypeVacuum})
if assignedVacuumTask == nil || assignedVacuumTask.Type != types.TaskTypeVacuum {
t.Error("Vacuum worker should get vacuum task")
}
// Test wrong capability - should get nothing
wrongTask := scheduler.GetNextTask("ec_worker", []types.TaskType{types.TaskTypeVacuum})
if wrongTask != nil {
t.Error("EC worker should not get vacuum task")
}
t.Log("✅ Capability matching test passed")
}
func TestTaskAssignment_PriorityOrdering(t *testing.T) {
queue := NewPriorityTaskQueue()
// Add tasks in reverse priority order
lowTask := &types.Task{
ID: "low_task",
Priority: types.TaskPriorityLow,
}
highTask := &types.Task{
ID: "high_task",
Priority: types.TaskPriorityHigh,
}
normalTask := &types.Task{
ID: "normal_task",
Priority: types.TaskPriorityNormal,
}
queue.Push(lowTask)
queue.Push(normalTask)
queue.Push(highTask)
// Should get high priority first
first := queue.Pop()
if first.Priority != types.TaskPriorityHigh {
t.Errorf("Expected high priority first, got %d", first.Priority)
}
// Then normal priority
second := queue.Pop()
if second.Priority != types.TaskPriorityNormal {
t.Errorf("Expected normal priority second, got %d", second.Priority)
}
// Finally low priority
third := queue.Pop()
if third.Priority != types.TaskPriorityLow {
t.Errorf("Expected low priority third, got %d", third.Priority)
}
t.Log("✅ Priority ordering test passed")
}
func TestTaskAssignment_WorkerCapacityLimits(t *testing.T) {
registry := NewWorkerRegistry()
// Register worker with limited capacity
worker := &types.Worker{
ID: "limited_worker",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
MaxConcurrent: 2,
Status: "active",
CurrentLoad: 2, // Already at capacity
}
registry.RegisterWorker(worker)
// Worker should not be available
availableWorkers := registry.GetAvailableWorkers()
if len(availableWorkers) != 0 {
t.Error("Worker at capacity should not be available")
}
// Reduce load
worker.CurrentLoad = 1
// Worker should now be available
availableWorkers = registry.GetAvailableWorkers()
if len(availableWorkers) != 1 {
t.Error("Worker with capacity should be available")
}
t.Log("✅ Worker capacity limits test passed")
}
func TestTaskAssignment_ScheduledTasks(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
worker := &types.Worker{
ID: "worker1",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
CurrentLoad: 0,
}
registry.RegisterWorker(worker)
// Create task scheduled for future
futureTask := &types.Task{
ID: "future_task",
Type: types.TaskTypeVacuum,
ScheduledAt: time.Now().Add(1 * time.Hour), // 1 hour from now
}
// Create task ready now
readyTask := &types.Task{
ID: "ready_task",
Type: types.TaskTypeVacuum,
ScheduledAt: time.Now().Add(-1 * time.Minute), // 1 minute ago
}
queue.Push(futureTask)
queue.Push(readyTask)
// Should get ready task, not future task
assignedTask := scheduler.GetNextTask("worker1", []types.TaskType{types.TaskTypeVacuum})
if assignedTask == nil || assignedTask.ID != "ready_task" {
t.Error("Should assign ready task, not future scheduled task")
}
t.Log("✅ Scheduled tasks test passed")
}
func TestTaskAssignment_WorkerSelection(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
// Register workers with different characteristics
highPerformanceWorker := &types.Worker{
ID: "high_perf_worker",
Address: "server1",
Capabilities: []types.TaskType{types.TaskTypeErasureCoding},
Status: "active",
CurrentLoad: 0,
MaxConcurrent: 4,
}
lowPerformanceWorker := &types.Worker{
ID: "low_perf_worker",
Address: "server2",
Capabilities: []types.TaskType{types.TaskTypeErasureCoding},
Status: "active",
CurrentLoad: 1,
MaxConcurrent: 2,
}
registry.RegisterWorker(highPerformanceWorker)
registry.RegisterWorker(lowPerformanceWorker)
// Set up metrics to favor high performance worker
registry.metrics[highPerformanceWorker.ID] = &WorkerMetrics{
TasksCompleted: 100,
TasksFailed: 5,
SuccessRate: 0.95,
AverageTaskTime: 10 * time.Minute,
LastTaskTime: time.Now().Add(-5 * time.Minute),
}
registry.metrics[lowPerformanceWorker.ID] = &WorkerMetrics{
TasksCompleted: 50,
TasksFailed: 10,
SuccessRate: 0.83,
AverageTaskTime: 20 * time.Minute,
LastTaskTime: time.Now().Add(-1 * time.Hour),
}
// Create high priority task
task := &types.Task{
ID: "important_task",
Type: types.TaskTypeErasureCoding,
Priority: types.TaskPriorityHigh,
Server: "server1", // Prefers server1
}
availableWorkers := []*types.Worker{highPerformanceWorker, lowPerformanceWorker}
selectedWorker := scheduler.SelectWorker(task, availableWorkers)
if selectedWorker == nil {
t.Fatal("No worker selected")
}
if selectedWorker.ID != "high_perf_worker" {
t.Errorf("Expected high performance worker to be selected, got %s", selectedWorker.ID)
}
t.Log("✅ Worker selection test passed")
}
func TestTaskAssignment_ServerAffinity(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
// Workers on different servers
worker1 := &types.Worker{
ID: "worker1",
Address: "server1",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
CurrentLoad: 0,
}
worker2 := &types.Worker{
ID: "worker2",
Address: "server2",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
CurrentLoad: 0,
}
registry.RegisterWorker(worker1)
registry.RegisterWorker(worker2)
// Task that prefers server1
task := &types.Task{
ID: "affinity_task",
Type: types.TaskTypeVacuum,
Server: "server1", // Should prefer worker on server1
}
availableWorkers := []*types.Worker{worker1, worker2}
selectedWorker := scheduler.SelectWorker(task, availableWorkers)
if selectedWorker == nil {
t.Fatal("No worker selected")
}
if selectedWorker.Address != "server1" {
t.Errorf("Expected worker on server1 to be selected for server affinity")
}
t.Log("✅ Server affinity test passed")
}
func TestTaskAssignment_DuplicateTaskPrevention(t *testing.T) {
queue := NewPriorityTaskQueue()
// Add initial task
task1 := &types.Task{
ID: "task1",
Type: types.TaskTypeVacuum,
VolumeID: 1,
}
queue.Push(task1)
// Check for duplicate
hasDuplicate := queue.HasTask(1, types.TaskTypeVacuum)
if !hasDuplicate {
t.Error("Should detect existing task for volume")
}
// Check for non-existent task
hasNonExistent := queue.HasTask(2, types.TaskTypeVacuum)
if hasNonExistent {
t.Error("Should not detect task for different volume")
}
// Check for different task type
hasDifferentType := queue.HasTask(1, types.TaskTypeErasureCoding)
if hasDifferentType {
t.Error("Should not detect different task type for same volume")
}
t.Log("✅ Duplicate task prevention test passed")
}
func TestTaskAssignment_TaskRemoval(t *testing.T) {
queue := NewPriorityTaskQueue()
// Add tasks
task1 := &types.Task{ID: "task1", Priority: types.TaskPriorityNormal}
task2 := &types.Task{ID: "task2", Priority: types.TaskPriorityHigh}
task3 := &types.Task{ID: "task3", Priority: types.TaskPriorityLow}
queue.Push(task1)
queue.Push(task2)
queue.Push(task3)
if queue.Size() != 3 {
t.Errorf("Expected queue size 3, got %d", queue.Size())
}
// Remove middle priority task
removed := queue.RemoveTask("task1")
if !removed {
t.Error("Should have removed task1")
}
if queue.Size() != 2 {
t.Errorf("Expected queue size 2 after removal, got %d", queue.Size())
}
// Verify order maintained (high priority first)
next := queue.Peek()
if next.ID != "task2" {
t.Errorf("Expected task2 (high priority) to be next, got %s", next.ID)
}
t.Log("✅ Task removal test passed")
}
func TestTaskAssignment_EdgeCases(t *testing.T) {
t.Run("EmptyQueue", func(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
worker := &types.Worker{
ID: "worker1",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
}
registry.RegisterWorker(worker)
// Empty queue should return nil
task := scheduler.GetNextTask("worker1", []types.TaskType{types.TaskTypeVacuum})
if task != nil {
t.Error("Empty queue should return nil task")
}
})
t.Run("UnknownWorker", func(t *testing.T) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
task := &types.Task{ID: "task1", Type: types.TaskTypeVacuum}
queue.Push(task)
// Unknown worker should return nil
assignedTask := scheduler.GetNextTask("unknown_worker", []types.TaskType{types.TaskTypeVacuum})
if assignedTask != nil {
t.Error("Unknown worker should not get tasks")
}
})
t.Run("InactiveWorker", func(t *testing.T) {
registry := NewWorkerRegistry()
worker := &types.Worker{
ID: "inactive_worker",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "inactive",
CurrentLoad: 0,
}
registry.RegisterWorker(worker)
// Inactive worker should not be available
available := registry.GetAvailableWorkers()
if len(available) != 0 {
t.Error("Inactive worker should not be available")
}
})
t.Log("✅ Edge cases test passed")
}
// Performance test for task assignment
func BenchmarkTaskAssignment_GetNextTask(b *testing.B) {
registry := NewWorkerRegistry()
queue := NewPriorityTaskQueue()
scheduler := NewTaskScheduler(registry, queue)
// Setup worker
worker := &types.Worker{
ID: "bench_worker",
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
CurrentLoad: 0,
}
registry.RegisterWorker(worker)
// Add many tasks
for i := 0; i < 1000; i++ {
task := &types.Task{
ID: fmt.Sprintf("task_%d", i),
Type: types.TaskTypeVacuum,
Priority: types.TaskPriorityNormal,
}
queue.Push(task)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
scheduler.GetNextTask("bench_worker", []types.TaskType{types.TaskTypeVacuum})
}
}
func BenchmarkTaskAssignment_WorkerSelection(b *testing.B) {
registry := NewWorkerRegistry()
scheduler := NewTaskScheduler(registry, nil)
// Create many workers
workers := make([]*types.Worker, 100)
for i := 0; i < 100; i++ {
worker := &types.Worker{
ID: fmt.Sprintf("worker_%d", i),
Capabilities: []types.TaskType{types.TaskTypeVacuum},
Status: "active",
CurrentLoad: i % 3, // Varying loads
}
registry.RegisterWorker(worker)
workers[i] = worker
}
task := &types.Task{
ID: "bench_task",
Type: types.TaskTypeVacuum,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
scheduler.SelectWorker(task, workers)
}
}