mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
210 lines
7.9 KiB
Go
210 lines
7.9 KiB
Go
package maintenance
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/worker_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/worker/tasks"
|
|
"github.com/seaweedfs/seaweedfs/weed/worker/types"
|
|
)
|
|
|
|
// The detectors and schedulers live in a process-global registry, so these tests restore
|
|
// whatever they change. Otherwise a test that disables a task leaks that state into every
|
|
// later test in the package.
|
|
func snapshotDetectorState(t *testing.T) {
|
|
t.Helper()
|
|
|
|
registry := tasks.GetGlobalTypesRegistry()
|
|
enabled := make(map[types.TaskType]bool)
|
|
maxConcurrent := make(map[types.TaskType]int)
|
|
for taskType, detector := range registry.GetAllDetectors() {
|
|
enabled[taskType] = detector.IsEnabled()
|
|
}
|
|
for taskType, scheduler := range registry.GetAllSchedulers() {
|
|
maxConcurrent[taskType] = scheduler.GetMaxConcurrent()
|
|
}
|
|
|
|
t.Cleanup(func() {
|
|
for taskType, detector := range registry.GetAllDetectors() {
|
|
if setter, ok := detector.(interface{ SetEnabled(bool) }); ok {
|
|
setter.SetEnabled(enabled[taskType])
|
|
}
|
|
}
|
|
for taskType, scheduler := range registry.GetAllSchedulers() {
|
|
if setter, ok := scheduler.(interface{ SetMaxConcurrent(int) }); ok {
|
|
setter.SetMaxConcurrent(maxConcurrent[taskType])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestPolicyReachesRegisteredDetectors is the regression test for the half of the scan-flood bug
|
|
// that a corrected policy alone did not fix: MaintenanceIntegration pushes the policy into
|
|
// detectors and schedulers through interface{ SetEnabled(bool) } and
|
|
// interface{ SetMaxConcurrent(int) } type assertions, but every task is backed by
|
|
// base.GenericDetector/base.GenericScheduler, which implemented neither. The assertions
|
|
// failed silently for every task on every startup, so the policy never reached
|
|
// detector.IsEnabled() - which is what ScanWithTaskDetectors gates scanning on.
|
|
func TestPolicyReachesRegisteredDetectors(t *testing.T) {
|
|
snapshotDetectorState(t)
|
|
|
|
registry := tasks.GetGlobalTypesRegistry()
|
|
if len(registry.GetAllDetectors()) == 0 {
|
|
t.Fatal("no detectors registered, the test cannot prove anything")
|
|
}
|
|
|
|
// Enable everything first so the disabling below cannot pass by accident.
|
|
policy := policyWithAllTasks(t, true)
|
|
NewMaintenanceIntegration(NewMaintenanceQueue(policy), policy)
|
|
|
|
for taskType, detector := range registry.GetAllDetectors() {
|
|
if !detector.IsEnabled() {
|
|
t.Fatalf("detector %s is disabled after an all-enabled policy was applied", taskType)
|
|
}
|
|
}
|
|
|
|
// Now disable everything through the policy and check it lands on the detectors.
|
|
policy = policyWithAllTasks(t, false)
|
|
NewMaintenanceIntegration(NewMaintenanceQueue(policy), policy)
|
|
|
|
for taskType, detector := range registry.GetAllDetectors() {
|
|
if detector.IsEnabled() {
|
|
t.Errorf("detector %s still reports enabled after the policy disabled it; "+
|
|
"the policy is not reaching the flag ScanWithTaskDetectors gates on", taskType)
|
|
}
|
|
}
|
|
for taskType, scheduler := range registry.GetAllSchedulers() {
|
|
if scheduler.IsEnabled() {
|
|
t.Errorf("scheduler %s still reports enabled after the policy disabled it", taskType)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPolicyMaxConcurrentReachesSchedulers covers the SetMaxConcurrent half of the same
|
|
// wiring. GetMaxConcurrent is what MaintenanceQueue.getMaxConcurrentForTaskType asks before
|
|
// starting another task of a type.
|
|
func TestPolicyMaxConcurrentReachesSchedulers(t *testing.T) {
|
|
snapshotDetectorState(t)
|
|
|
|
const wantMaxConcurrent = 7
|
|
|
|
policy := policyWithAllTasks(t, true)
|
|
for _, taskPolicy := range policy.TaskPolicies {
|
|
taskPolicy.MaxConcurrent = wantMaxConcurrent
|
|
}
|
|
NewMaintenanceIntegration(NewMaintenanceQueue(policy), policy)
|
|
|
|
registry := tasks.GetGlobalTypesRegistry()
|
|
for taskType, scheduler := range registry.GetAllSchedulers() {
|
|
if _, covered := policy.TaskPolicies[string(taskType)]; !covered {
|
|
continue
|
|
}
|
|
if got := scheduler.GetMaxConcurrent(); got != wantMaxConcurrent {
|
|
t.Errorf("scheduler %s max concurrent = %d, want %d from the policy", taskType, got, wantMaxConcurrent)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPolicyWithoutEntryLeavesTaskAlone guards the direction that would have been a silent
|
|
// outage: IsTaskEnabled reports false for a task type the policy does not list, so applying
|
|
// it unconditionally would disable every task the policy has no entry for.
|
|
func TestPolicyWithoutEntryLeavesTaskAlone(t *testing.T) {
|
|
snapshotDetectorState(t)
|
|
|
|
registry := tasks.GetGlobalTypesRegistry()
|
|
|
|
// Start from a policy that enables everything.
|
|
enabling := policyWithAllTasks(t, true)
|
|
NewMaintenanceIntegration(NewMaintenanceQueue(enabling), enabling)
|
|
|
|
// An empty policy has an entry for nothing at all.
|
|
empty := &MaintenancePolicy{TaskPolicies: make(map[string]*worker_pb.TaskPolicy)}
|
|
NewMaintenanceIntegration(NewMaintenanceQueue(empty), empty)
|
|
|
|
for taskType, detector := range registry.GetAllDetectors() {
|
|
if !detector.IsEnabled() {
|
|
t.Errorf("detector %s was disabled by a policy that has no entry for it; "+
|
|
"a missing entry means no opinion, not disabled", taskType)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBuildPolicyCoversEveryRegisteredTask keeps BuildPolicyFromTaskConfigs and the task
|
|
// registry in step. A registered task with no policy entry has no enabled flag and no
|
|
// concurrency limit of its own, which is the state ec_balance was in.
|
|
func TestBuildPolicyCoversEveryRegisteredTask(t *testing.T) {
|
|
policy := BuildPolicyFromTaskConfigs(nil)
|
|
|
|
for taskType := range tasks.GetGlobalTypesRegistry().GetAllDetectors() {
|
|
if _, ok := policy.TaskPolicies[string(taskType)]; !ok {
|
|
t.Errorf("task %s is registered as a detector but BuildPolicyFromTaskConfigs "+
|
|
"builds no policy entry for it, so IsTaskEnabled reports false for it", taskType)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestBuildPolicyIncludesEcBalance pins the specific gap above.
|
|
func TestBuildPolicyIncludesEcBalance(t *testing.T) {
|
|
policy := BuildPolicyFromTaskConfigs(nil)
|
|
|
|
ecBalance := policy.TaskPolicies[string(types.TaskTypeECBalance)]
|
|
if ecBalance == nil {
|
|
t.Fatal("no ec_balance entry in the built maintenance policy")
|
|
}
|
|
if !IsTaskEnabled(policy, MaintenanceTaskType(types.TaskTypeECBalance)) {
|
|
t.Error("ec_balance reports disabled with no persisted config, want the compiled-in default of enabled")
|
|
}
|
|
if ecBalance.GetEcBalanceConfig() == nil {
|
|
t.Error("ec_balance policy entry carries no EcBalanceConfig")
|
|
}
|
|
}
|
|
|
|
// policyWithAllTasks builds a policy that has an entry for every registered task type,
|
|
// all sharing the same enabled flag.
|
|
func policyWithAllTasks(t *testing.T, enabled bool) *MaintenancePolicy {
|
|
t.Helper()
|
|
|
|
policy := &MaintenancePolicy{
|
|
GlobalMaxConcurrent: 4,
|
|
TaskPolicies: make(map[string]*worker_pb.TaskPolicy),
|
|
}
|
|
for taskType := range tasks.GetGlobalTypesRegistry().GetAllDetectors() {
|
|
policy.TaskPolicies[string(taskType)] = &worker_pb.TaskPolicy{
|
|
Enabled: enabled,
|
|
MaxConcurrent: 1,
|
|
RepeatIntervalSeconds: 3600,
|
|
}
|
|
}
|
|
return policy
|
|
}
|
|
|
|
// TestPolicyHelpersTolerateNilPolicy pins the nil handling in the exported policy helpers.
|
|
// MaintenanceConfig.Policy is nil until something builds one, and UpdateConfig accepts a
|
|
// config that has none, so these are reachable with a nil policy - GetTaskPolicy used to
|
|
// dereference it and take the admin process down.
|
|
func TestPolicyHelpersTolerateNilPolicy(t *testing.T) {
|
|
const taskType = MaintenanceTaskType("balance")
|
|
|
|
if got := GetTaskPolicy(nil, taskType); got != nil {
|
|
t.Errorf("GetTaskPolicy(nil) = %v, want nil", got)
|
|
}
|
|
if IsTaskEnabled(nil, taskType) {
|
|
t.Error("IsTaskEnabled(nil) = true, want false")
|
|
}
|
|
if got := GetMaxConcurrent(nil, taskType); got != 1 {
|
|
t.Errorf("GetMaxConcurrent(nil) = %d, want the safe default 1", got)
|
|
}
|
|
if got := GetRepeatInterval(nil, taskType); got != 0 {
|
|
t.Errorf("GetRepeatInterval(nil) = %d, want 0 so callers fall back to their own default", got)
|
|
}
|
|
|
|
// A policy that exists but lists nothing must behave the same way.
|
|
empty := &MaintenancePolicy{}
|
|
if got := GetTaskPolicy(empty, taskType); got != nil {
|
|
t.Errorf("GetTaskPolicy(empty) = %v, want nil", got)
|
|
}
|
|
if IsTaskEnabled(empty, taskType) {
|
|
t.Error("IsTaskEnabled(empty) = true, want false")
|
|
}
|
|
}
|