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
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)
|
|
}
|
|
}
|