Files
minio/internal/config/ilm/ilm_test.go
T
mr javad seydi 7a060cab1e feat(ilm): relocate hot objects across server pools by GET frequency
Keep NVMe/HDD pool pairs useful without remote tiering: promote objects that
are read often, demote previously moved objects once they go idle, and leave
the feature off until operators set a two-pool topology.

Signed-off-by: mr javad seydi <seydi.birjand@gmail.com>
2026-08-15 13:48:22 +03:30

110 lines
3.2 KiB
Go

// Copyright (c) 2015-2026 MinIO, Inc.
package ilm
import (
"errors"
"reflect"
"testing"
"time"
"github.com/minio/minio/internal/config"
)
func clearAccessEnv(t *testing.T) {
t.Helper()
for _, name := range []string{
EnvILMAccessTiering, EnvILMAccessPools, EnvILMAccessMaxSize,
EnvILMAccessPromoteWatermark, EnvILMAccessBinWidth, EnvILMAccessBins,
EnvILMAccessFlush, EnvILMAccessMinResidency, EnvILMAccessWorkers,
EnvILMAccessMaxTracked,
} {
// The env helper treats an empty value as unset. t.Setenv restores the
// caller's exact value automatically when the test finishes.
t.Setenv(name, "")
}
}
func TestLookupAccessDefaults(t *testing.T) {
clearAccessEnv(t)
cfg, err := LookupConfig(DefaultKVS.Clone())
if err != nil {
t.Fatal(err)
}
if cfg.AccessTiering {
t.Fatal("access tiering must default off")
}
if cfg.AccessBinWidth != time.Minute || cfg.AccessBins != 12 || cfg.AccessFlush != time.Minute {
t.Fatalf("unexpected counter defaults: width=%s bins=%d flush=%s", cfg.AccessBinWidth, cfg.AccessBins, cfg.AccessFlush)
}
if cfg.AccessWorkers != 10 || cfg.AccessMaxTracked != 1000000 {
t.Fatalf("unexpected worker/map defaults: %d/%d", cfg.AccessWorkers, cfg.AccessMaxTracked)
}
}
func TestLookupAccessEnabled(t *testing.T) {
clearAccessEnv(t)
kvs := DefaultKVS.Clone()
kvs.Set(accessTiering, config.EnableOn)
kvs.Set(accessPools, "2, 0, 1")
kvs.Set(accessMaxSize, "2GiB")
kvs.Set(accessPromoteWatermark, "90")
kvs.Set(accessBinWidth, "30s")
kvs.Set(accessBins, "20")
kvs.Set(accessFlush, "15s")
kvs.Set(accessMinResidency, "2h")
kvs.Set(accessWorkers, "7")
kvs.Set(accessMaxTracked, "1234")
cfg, err := LookupConfig(kvs)
if err != nil {
t.Fatal(err)
}
if !cfg.AccessTiering || !reflect.DeepEqual(cfg.AccessPools, []int{2, 0, 1}) {
t.Fatalf("unexpected topology: enabled=%v pools=%v", cfg.AccessTiering, cfg.AccessPools)
}
if cfg.AccessMaxSize != 2<<30 || cfg.HistoryWindow() != 10*time.Minute {
t.Fatalf("unexpected size/history: %d/%s", cfg.AccessMaxSize, cfg.HistoryWindow())
}
if hot, ok := cfg.HotPool(); !ok || hot != 2 {
t.Fatalf("HotPool = %d/%v", hot, ok)
}
if cold, ok := cfg.ColdPool(); !ok || cold != 1 {
t.Fatalf("ColdPool = %d/%v", cold, ok)
}
}
func TestLookupAccessRejectsInvalidValues(t *testing.T) {
clearAccessEnv(t)
tests := []struct {
key, value string
want error
}{
{accessPools, "0,0", ErrAccessPoolsInvalid},
{accessPromoteWatermark, "0", ErrAccessWatermarkInvalid},
{accessBinWidth, "500ms", ErrAccessBinWidthInvalid},
{accessBins, "1", ErrAccessBinsInvalid},
{accessFlush, "0s", ErrAccessFlushInvalid},
{accessMinResidency, "-1s", ErrAccessResidencyInvalid},
{accessWorkers, "0", ErrAccessWorkersInvalid},
{accessMaxTracked, "0", ErrAccessTrackedInvalid},
}
for _, tc := range tests {
t.Run(tc.key, func(t *testing.T) {
kvs := DefaultKVS.Clone()
kvs.Set(tc.key, tc.value)
_, err := LookupConfig(kvs)
if !errors.Is(err, tc.want) {
t.Fatalf("error = %v, want %v", err, tc.want)
}
})
}
kvs := DefaultKVS.Clone()
kvs.Set(accessTiering, config.EnableOn)
kvs.Set(accessPools, "0")
if _, err := LookupConfig(kvs); !errors.Is(err, ErrAccessPoolsTooFew) {
t.Fatalf("error = %v, want %v", err, ErrAccessPoolsTooFew)
}
}