mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-28 18:55:49 +00:00
added simulation as tests
This commit is contained in:
@@ -0,0 +1,509 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user