* Make EC detection context aware * Update register.go * Speed up EC detection planning * Add tests for EC detection planner * optimizations detection.go: extracted ParseCollectionFilter (exported) and feed it into the detection loop so both detection and tracing share the same parsing/whitelisting logic; the detection loop now iterates on a sorted list of volume IDs, checks the context at every iteration, and only sets hasMore when there are still unprocessed groups after hitting maxResults, keeping runtime bounded while still scheduling planned tasks before returning the results. erasure_coding_handler.go: dropped the duplicated inline filter parsing in emitErasureCodingDetectionDecisionTrace and now reuse erasurecodingtask.ParseCollectionFilter, and the summary suffix logic now only accounts for the hasMore case that can actually happen. detection_test.go: updated the helper topology builder to use master_pb.VolumeInformationMessage (matching the current protobuf types) and tightened the cancellation/max-results tests so they reliably exercise the detection logic (cancel before calling Detection, and provide enough disks so one result is produced before the limit). * use working directory * fix compilation * fix compilation * rename * go vet * fix getenv * address comments, fix error
244 lines
7.7 KiB
Go
244 lines
7.7 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
pluginworker "github.com/seaweedfs/seaweedfs/weed/plugin/worker"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
)
|
|
|
|
func TestBuildPluginWorkerHandler(t *testing.T) {
|
|
dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
|
|
|
|
testMaxConcurrency := int(pluginworker.DefaultMaxExecutionConcurrency)
|
|
|
|
handler, err := buildPluginWorkerHandler("vacuum", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandler(vacuum) err = %v", err)
|
|
}
|
|
if handler == nil {
|
|
t.Fatalf("expected non-nil handler")
|
|
}
|
|
|
|
handler, err = buildPluginWorkerHandler("", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandler(default) err = %v", err)
|
|
}
|
|
if handler == nil {
|
|
t.Fatalf("expected non-nil default handler")
|
|
}
|
|
|
|
handler, err = buildPluginWorkerHandler("volume_balance", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandler(volume_balance) err = %v", err)
|
|
}
|
|
if handler == nil {
|
|
t.Fatalf("expected non-nil volume_balance handler")
|
|
}
|
|
|
|
handler, err = buildPluginWorkerHandler("balance", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandler(balance alias) err = %v", err)
|
|
}
|
|
if handler == nil {
|
|
t.Fatalf("expected non-nil balance alias handler")
|
|
}
|
|
|
|
handler, err = buildPluginWorkerHandler("erasure_coding", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandler(erasure_coding) err = %v", err)
|
|
}
|
|
if handler == nil {
|
|
t.Fatalf("expected non-nil erasure_coding handler")
|
|
}
|
|
|
|
handler, err = buildPluginWorkerHandler("ec", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandler(ec alias) err = %v", err)
|
|
}
|
|
if handler == nil {
|
|
t.Fatalf("expected non-nil ec alias handler")
|
|
}
|
|
|
|
_, err = buildPluginWorkerHandler("unknown", dialOption, testMaxConcurrency, "")
|
|
if err == nil {
|
|
t.Fatalf("expected unsupported job type error")
|
|
}
|
|
}
|
|
|
|
func TestBuildPluginWorkerHandlers(t *testing.T) {
|
|
dialOption := grpc.WithTransportCredentials(insecure.NewCredentials())
|
|
|
|
testMaxConcurrency := int(pluginworker.DefaultMaxExecutionConcurrency)
|
|
|
|
handlers, err := buildPluginWorkerHandlers("vacuum,volume_balance,erasure_coding", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandlers(list) err = %v", err)
|
|
}
|
|
if len(handlers) != 3 {
|
|
t.Fatalf("expected 3 handlers, got %d", len(handlers))
|
|
}
|
|
|
|
handlers, err = buildPluginWorkerHandlers("balance,ec,vacuum,balance", dialOption, testMaxConcurrency, "")
|
|
if err != nil {
|
|
t.Fatalf("buildPluginWorkerHandlers(aliases) err = %v", err)
|
|
}
|
|
if len(handlers) != 3 {
|
|
t.Fatalf("expected deduped 3 handlers, got %d", len(handlers))
|
|
}
|
|
|
|
_, err = buildPluginWorkerHandlers("unknown,vacuum", dialOption, testMaxConcurrency, "")
|
|
if err == nil {
|
|
t.Fatalf("expected unsupported job type error")
|
|
}
|
|
}
|
|
|
|
func TestParsePluginWorkerJobTypes(t *testing.T) {
|
|
jobTypes, err := parsePluginWorkerJobTypes("")
|
|
if err != nil {
|
|
t.Fatalf("parsePluginWorkerJobTypes(default) err = %v", err)
|
|
}
|
|
if len(jobTypes) != 1 || jobTypes[0] != "vacuum" {
|
|
t.Fatalf("expected default [vacuum], got %v", jobTypes)
|
|
}
|
|
|
|
jobTypes, err = parsePluginWorkerJobTypes(" volume_balance , ec , vacuum , volume_balance ")
|
|
if err != nil {
|
|
t.Fatalf("parsePluginWorkerJobTypes(list) err = %v", err)
|
|
}
|
|
if len(jobTypes) != 3 {
|
|
t.Fatalf("expected 3 deduped job types, got %d (%v)", len(jobTypes), jobTypes)
|
|
}
|
|
if jobTypes[0] != "volume_balance" || jobTypes[1] != "erasure_coding" || jobTypes[2] != "vacuum" {
|
|
t.Fatalf("unexpected parsed order %v", jobTypes)
|
|
}
|
|
|
|
if _, err = parsePluginWorkerJobTypes(" , "); err != nil {
|
|
t.Fatalf("expected empty list to resolve to default vacuum: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPluginWorkerDefaultJobTypes(t *testing.T) {
|
|
jobTypes, err := parsePluginWorkerJobTypes(defaultPluginWorkerJobTypes)
|
|
if err != nil {
|
|
t.Fatalf("parsePluginWorkerJobTypes(default setting) err = %v", err)
|
|
}
|
|
if len(jobTypes) != 3 {
|
|
t.Fatalf("expected default job types to include 3 handlers, got %v", jobTypes)
|
|
}
|
|
}
|
|
|
|
func TestResolvePluginWorkerID(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
explicit, err := resolvePluginWorkerID("worker-x", dir)
|
|
if err != nil {
|
|
t.Fatalf("resolvePluginWorkerID(explicit) err = %v", err)
|
|
}
|
|
if explicit != "worker-x" {
|
|
t.Fatalf("expected explicit id, got %q", explicit)
|
|
}
|
|
|
|
generated, err := resolvePluginWorkerID("", dir)
|
|
if err != nil {
|
|
t.Fatalf("resolvePluginWorkerID(generate) err = %v", err)
|
|
}
|
|
if generated == "" {
|
|
t.Fatalf("expected generated id")
|
|
}
|
|
if len(generated) < 7 || generated[:7] != "plugin-" {
|
|
t.Fatalf("expected generated id prefix plugin-, got %q", generated)
|
|
}
|
|
|
|
persistedPath := filepath.Join(dir, "plugin.worker.id")
|
|
if _, statErr := os.Stat(persistedPath); statErr != nil {
|
|
t.Fatalf("expected persisted worker id file: %v", statErr)
|
|
}
|
|
|
|
reused, err := resolvePluginWorkerID("", dir)
|
|
if err != nil {
|
|
t.Fatalf("resolvePluginWorkerID(reuse) err = %v", err)
|
|
}
|
|
if reused != generated {
|
|
t.Fatalf("expected reused id %q, got %q", generated, reused)
|
|
}
|
|
}
|
|
|
|
func TestParsePluginWorkerAdminAddress(t *testing.T) {
|
|
host, httpPort, hasExplicitGrpcPort, err := parsePluginWorkerAdminAddress("localhost:23646")
|
|
if err != nil {
|
|
t.Fatalf("parsePluginWorkerAdminAddress(localhost:23646) err = %v", err)
|
|
}
|
|
if host != "localhost" || httpPort != 23646 || hasExplicitGrpcPort {
|
|
t.Fatalf("unexpected parse result: host=%q httpPort=%d hasExplicit=%v", host, httpPort, hasExplicitGrpcPort)
|
|
}
|
|
|
|
host, httpPort, hasExplicitGrpcPort, err = parsePluginWorkerAdminAddress("localhost:23646.33646")
|
|
if err != nil {
|
|
t.Fatalf("parsePluginWorkerAdminAddress(localhost:23646.33646) err = %v", err)
|
|
}
|
|
if host != "localhost" || httpPort != 23646 || !hasExplicitGrpcPort {
|
|
t.Fatalf("unexpected dotted parse result: host=%q httpPort=%d hasExplicit=%v", host, httpPort, hasExplicitGrpcPort)
|
|
}
|
|
|
|
if _, _, _, err = parsePluginWorkerAdminAddress("localhost"); err == nil {
|
|
t.Fatalf("expected parse error for invalid address")
|
|
}
|
|
}
|
|
|
|
func TestResolvePluginWorkerAdminServerUsesStatusGrpcPort(t *testing.T) {
|
|
const grpcPort = 35432
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/plugin/status" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(fmt.Sprintf(`{"worker_grpc_port":%d}`, grpcPort)))
|
|
}))
|
|
defer server.Close()
|
|
|
|
adminAddress := strings.TrimPrefix(server.URL, "http://")
|
|
host, httpPort, _, err := parsePluginWorkerAdminAddress(adminAddress)
|
|
if err != nil {
|
|
t.Fatalf("parsePluginWorkerAdminAddress(%s) err = %v", adminAddress, err)
|
|
}
|
|
|
|
resolved := resolvePluginWorkerAdminServer(adminAddress)
|
|
expected := fmt.Sprintf("%s:%d.%d", host, httpPort, grpcPort)
|
|
if resolved != expected {
|
|
t.Fatalf("unexpected resolved admin address: got=%q want=%q", resolved, expected)
|
|
}
|
|
}
|
|
|
|
func TestResolvePluginWorkerAdminServerKeepsDefaultGrpcOffset(t *testing.T) {
|
|
var server *httptest.Server
|
|
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/plugin/status" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
address := strings.TrimPrefix(server.URL, "http://")
|
|
_, httpPort, _, parseErr := parsePluginWorkerAdminAddress(address)
|
|
if parseErr != nil {
|
|
http.Error(w, parseErr.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(fmt.Sprintf(`{"worker_grpc_port":%d}`, httpPort+10000)))
|
|
}))
|
|
defer server.Close()
|
|
|
|
adminAddress := strings.TrimPrefix(server.URL, "http://")
|
|
resolved := resolvePluginWorkerAdminServer(adminAddress)
|
|
if resolved != adminAddress {
|
|
t.Fatalf("expected admin address to remain unchanged, got=%q want=%q", resolved, adminAddress)
|
|
}
|
|
}
|