package pluginworker import ( "fmt" "strings" "sync" "google.golang.org/grpc" ) // JobCategory groups job types by resource profile so that workers can be // configured with a category name instead of an explicit list of job types. type JobCategory string const ( CategoryAll JobCategory = "all" // pseudo-category matching every handler CategoryDefault JobCategory = "default" // lightweight, safe for any worker CategoryHeavy JobCategory = "heavy" // resource-intensive jobs ) // HandlerFactory describes how to build a JobHandler for a single job type. type HandlerFactory struct { // JobType is the canonical job type string (e.g. "vacuum"). JobType string // Category controls which category label selects this handler. Category JobCategory // Aliases are alternative CLI names that resolve to this job type // (e.g. "ec" for "erasure_coding"). Aliases []string // Build constructs the JobHandler. Build func(opts HandlerBuildOptions) (JobHandler, error) } // HandlerBuildOptions carries parameters forwarded from the CLI to handler // constructors. type HandlerBuildOptions struct { GrpcDialOption grpc.DialOption MaxExecute int WorkingDir string } var ( registryMu sync.Mutex registry []HandlerFactory ) // RegisterHandler adds a handler factory to the global registry. // It is intended to be called from handler init() functions. func RegisterHandler(f HandlerFactory) { registryMu.Lock() defer registryMu.Unlock() registry = append(registry, f) } // ResolveHandlerFactories takes a comma-separated token list that can contain // category names ("all", "default", "heavy") and/or explicit job type names // (including aliases). It returns a deduplicated, ordered slice of factories. func ResolveHandlerFactories(tokens string) ([]HandlerFactory, error) { registryMu.Lock() snapshot := make([]HandlerFactory, len(registry)) copy(snapshot, registry) registryMu.Unlock() parts := strings.Split(tokens, ",") result := make([]HandlerFactory, 0, len(snapshot)) seen := make(map[string]bool) for _, raw := range parts { tok := strings.ToLower(strings.TrimSpace(raw)) if tok == "" { continue } if cat, ok := tokenAsCategory(tok); ok { for _, f := range snapshot { if cat == CategoryAll || f.Category == cat { if !seen[f.JobType] { seen[f.JobType] = true result = append(result, f) } } } continue } f, err := findFactory(snapshot, tok) if err != nil { return nil, err } if !seen[f.JobType] { seen[f.JobType] = true result = append(result, f) } } if len(result) == 0 { return nil, fmt.Errorf("no job types resolved from %q", tokens) } return result, nil } // tokenAsCategory returns the category and true when tok is a known category // keyword. "all" is treated as a special pseudo-category that matches every // registered handler. func tokenAsCategory(tok string) (JobCategory, bool) { switch tok { case string(CategoryAll): return CategoryAll, true case string(CategoryDefault): return CategoryDefault, true case string(CategoryHeavy): return CategoryHeavy, true default: return "", false } } func findFactory(factories []HandlerFactory, tok string) (HandlerFactory, error) { for _, f := range factories { if strings.EqualFold(f.JobType, tok) { return f, nil } for _, alias := range f.Aliases { if strings.EqualFold(alias, tok) { return f, nil } } } return HandlerFactory{}, fmt.Errorf("unknown job type %q", tok) }