Files
seaweedfs/weed/admin/dash/task_policy_defaults_test.go
T
Junker der Provinz d86c692a39 admin: make the maintenance policy actually reach the task detectors
Loading the persisted task configs into the maintenance policy only
matters if something reads that policy, and nothing did.

MaintenanceIntegration pushes the policy into every registered detector
and scheduler through interface{ SetEnabled(bool) } and
interface{ SetMaxConcurrent(int) } type assertions. Every task registered
through base.RegisterTask is backed by base.GenericDetector and
base.GenericScheduler, and neither implemented either method, so all four
assertions failed silently for every task on every startup. The policy's
enabled flag reached nothing: ScanWithTaskDetectors gates on
detector.IsEnabled(), and the queue's policy lookups for max concurrent
and repeat interval are fallbacks that only fire when the scheduler
reports zero, which the generic scheduler never does.

Add the setters, delegating to the TaskConfig.SetEnabled the interface
already declares and to TaskDefinition.MaxConcurrent, which is what
GetMaxConcurrent returns.

Applying the policy required three more fixes, because with the
assertions working the policy could now do damage as well as good:

- IsTaskEnabled reports false for a task type the policy has no entry
  for, so applying it unconditionally would have disabled every task the
  policy does not list. Skip task types with no policy entry: no entry
  means no opinion, not disabled.

- ec_balance was exactly such a task. It is registered like the other
  three but had no entry in the policy builder and no accessor on
  ConfigPersistence at all, so its configuration could never be
  persisted. Add SaveEcBalanceTaskPolicy/LoadEcBalanceTaskPolicy, the
  task_ec_balance.pb file, the SaveTaskPolicy dispatcher case, and the
  policy entry.

- InitMaintenanceManager ran before loadTaskConfigurationsFromPersistence,
  which replaces each task's whole config object, so the policy was
  applied and then immediately thrown away. Swap the order. Both read the
  same files, so the policy is now the last writer and stays
  authoritative.

MaintenanceManager.UpdateConfig also updated the queue's and the
scanner's policy but not the integration's, so a policy changed at
runtime never reached the detectors. Add MaintenanceIntegration.SetPolicy
and call it.

While building the policy, stop hand-copying each task's fields and use
the task's own ToTaskPolicy(). The hand-written version was a second
definition of every task's policy and had already lost the erasure coding
preferred tags and replica placement and the balance IO rate limit. For
the same reason, the "nothing persisted yet" branches of
LoadVacuumTaskPolicy, LoadErasureCodingTaskPolicy and
LoadBalanceTaskPolicy now derive from each task's NewDefaultConfig()
instead of a third hand-written copy. Those copies had drifted, so with a
data directory but no config file on disk the effective defaults differed
from what the task and the admin UI schema both advertise:

  vacuum          scan interval  24h  -> 2h
  balance         scan interval   6h  -> 30m
  balance         imbalance      0.1  -> 0.2
  erasure coding  scan interval 168h  -> 1h
  erasure coding  fullness      0.90  -> 0.95
  erasure coding  min volume   1024MB -> 30MB

Finally, weed/admin/dash and weed/admin/maintenance each carried a copy
of the policy builder and they had already diverged. Export the
maintenance one as BuildPolicyFromTaskConfigs and have dash call it.

Refs #10874
2026-08-23 05:44:24 +02:00

179 lines
6.8 KiB
Go

package dash
import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/worker_pb"
"github.com/seaweedfs/seaweedfs/weed/worker/tasks/balance"
"github.com/seaweedfs/seaweedfs/weed/worker/tasks/ec_balance"
"github.com/seaweedfs/seaweedfs/weed/worker/tasks/erasure_coding"
"github.com/seaweedfs/seaweedfs/weed/worker/tasks/vacuum"
"google.golang.org/protobuf/proto"
)
// TestLoadTaskPolicyDefaultsMatchTaskDefaults pins the persistence layer's "nothing saved
// yet" defaults to each task's own NewDefaultConfig(). They used to be a second, hand-written
// copy and had drifted: with a data directory but no config file on disk, vacuum ran on a 24h
// scan interval instead of 2h, balance on 6h with a 0.1 imbalance threshold instead of 30m
// with 0.2, and erasure coding on 168h with a 0.90 fullness ratio and a 1024MB minimum volume
// size instead of 1h with 0.95 and 30MB - none of which is what the admin UI shows as the
// default for those fields.
func TestLoadTaskPolicyDefaultsMatchTaskDefaults(t *testing.T) {
cases := []struct {
name string
want *worker_pb.TaskPolicy
load func(cp *ConfigPersistence) (*worker_pb.TaskPolicy, error)
}{
{
name: "vacuum",
want: vacuum.NewDefaultConfig().ToTaskPolicy(),
load: func(cp *ConfigPersistence) (*worker_pb.TaskPolicy, error) { return cp.LoadVacuumTaskPolicy() },
},
{
name: "erasure_coding",
want: erasure_coding.NewDefaultConfig().ToTaskPolicy(),
load: func(cp *ConfigPersistence) (*worker_pb.TaskPolicy, error) {
return cp.LoadErasureCodingTaskPolicy()
},
},
{
name: "balance",
want: balance.NewDefaultConfig().ToTaskPolicy(),
load: func(cp *ConfigPersistence) (*worker_pb.TaskPolicy, error) { return cp.LoadBalanceTaskPolicy() },
},
{
name: "ec_balance",
want: ec_balance.NewDefaultConfig().ToTaskPolicy(),
load: func(cp *ConfigPersistence) (*worker_pb.TaskPolicy, error) { return cp.LoadEcBalanceTaskPolicy() },
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
// Both no-file branches have to agree with the task's own defaults: no data
// directory at all, and a data directory that has never been written to.
for _, cp := range []*ConfigPersistence{NewConfigPersistence(""), NewConfigPersistence(t.TempDir())} {
got, err := tc.load(cp)
if err != nil {
t.Fatalf("load %s policy: %v", tc.name, err)
}
if !proto.Equal(got, tc.want) {
t.Errorf("%s default policy (dataDir=%q) =\n %v\nwant NewDefaultConfig().ToTaskPolicy() =\n %v",
tc.name, cp.GetDataDir(), got, tc.want)
}
}
})
}
}
// TestLoadTaskConfigDefaultsMatchTaskDefaults covers the narrower Load*TaskConfig accessors,
// which carried a third copy of the same defaults.
func TestLoadTaskConfigDefaultsMatchTaskDefaults(t *testing.T) {
cp := NewConfigPersistence(t.TempDir())
vacuumConfig, err := cp.LoadVacuumTaskConfig()
if err != nil {
t.Fatalf("load vacuum config: %v", err)
}
if want := vacuum.NewDefaultConfig().ToTaskPolicy().GetVacuumConfig(); !proto.Equal(vacuumConfig, want) {
t.Errorf("vacuum default config = %v, want %v", vacuumConfig, want)
}
ecConfig, err := cp.LoadErasureCodingTaskConfig()
if err != nil {
t.Fatalf("load erasure coding config: %v", err)
}
if want := erasure_coding.NewDefaultConfig().ToTaskPolicy().GetErasureCodingConfig(); !proto.Equal(ecConfig, want) {
t.Errorf("erasure coding default config = %v, want %v", ecConfig, want)
}
balanceConfig, err := cp.LoadBalanceTaskConfig()
if err != nil {
t.Fatalf("load balance config: %v", err)
}
if want := balance.NewDefaultConfig().ToTaskPolicy().GetBalanceConfig(); !proto.Equal(balanceConfig, want) {
t.Errorf("balance default config = %v, want %v", balanceConfig, want)
}
}
// TestEcBalanceTaskPolicyRoundTrip checks the accessor ec_balance.LoadConfigFromPersistence
// asserts on. Before it existed, ec_balance was the one registered maintenance task whose
// configuration could not be persisted at all.
func TestEcBalanceTaskPolicyRoundTrip(t *testing.T) {
cp := NewConfigPersistence(t.TempDir())
saved := ec_balance.NewDefaultConfig()
saved.Enabled = false
saved.MinServerCount = 9
saved.ImbalanceThreshold = 0.42
saved.CollectionFilter = "pictures"
if err := cp.SaveEcBalanceTaskPolicy(saved.ToTaskPolicy()); err != nil {
t.Fatalf("save ec_balance policy: %v", err)
}
loaded := ec_balance.LoadConfigFromPersistence(cp)
if loaded == nil {
t.Fatal("ec_balance.LoadConfigFromPersistence returned nil")
}
if loaded.Enabled {
t.Error("ec_balance enabled = true, want the persisted false")
}
if loaded.MinServerCount != 9 {
t.Errorf("ec_balance min server count = %d, want the persisted 9", loaded.MinServerCount)
}
if loaded.ImbalanceThreshold != 0.42 {
t.Errorf("ec_balance imbalance threshold = %v, want the persisted 0.42", loaded.ImbalanceThreshold)
}
if loaded.CollectionFilter != "pictures" {
t.Errorf("ec_balance collection filter = %q, want the persisted %q", loaded.CollectionFilter, "pictures")
}
// The generic dispatcher the maintenance manager uses has to know the type too.
if err := cp.SaveTaskPolicy("ec_balance", saved.ToTaskPolicy()); err != nil {
t.Errorf("SaveTaskPolicy(ec_balance): %v", err)
}
}
// TestBuildPolicyKeepsTaskSpecificFields guards the fields the hand-written policy builder
// used to drop on the floor: the erasure coding preferred tags and replica placement, and
// the balance IO rate limit. Building each entry from the task's own ToTaskPolicy() keeps
// them, so a value set in admin.toml survives into the maintenance policy.
func TestBuildPolicyKeepsTaskSpecificFields(t *testing.T) {
cp := NewConfigPersistence(t.TempDir())
ecConfig := erasure_coding.NewDefaultConfig()
ecConfig.PreferredTags = []string{"ssd", "archive"}
ecConfig.ReplicaPlacement = "020"
if err := cp.SaveErasureCodingTaskPolicy(ecConfig.ToTaskPolicy()); err != nil {
t.Fatalf("save erasure coding policy: %v", err)
}
balanceConfig := balance.NewDefaultConfig()
balanceConfig.IoBytePerSecond = 5 << 20
if err := cp.SaveBalanceTaskPolicy(balanceConfig.ToTaskPolicy()); err != nil {
t.Fatalf("save balance policy: %v", err)
}
policy := cp.buildPolicyFromTaskConfigs()
ecPolicy := policy.TaskPolicies["erasure_coding"].GetErasureCodingConfig()
if ecPolicy == nil {
t.Fatal("no erasure coding config in the built policy")
}
if got := ecPolicy.GetReplicaPlacement(); got != "020" {
t.Errorf("erasure coding replica placement = %q, want the persisted %q", got, "020")
}
if got := ecPolicy.GetPreferredTags(); len(got) != 2 {
t.Errorf("erasure coding preferred tags = %v, want the persisted 2 entries", got)
}
balancePolicy := policy.TaskPolicies["balance"].GetBalanceConfig()
if balancePolicy == nil {
t.Fatal("no balance config in the built policy")
}
if got := balancePolicy.GetIoBytePerSecond(); got != 5<<20 {
t.Errorf("balance IO limit = %d, want the persisted %d", got, 5<<20)
}
}