You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							272 lines
						
					
					
						
							7.0 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							272 lines
						
					
					
						
							7.0 KiB
						
					
					
				| package base | |
| 
 | |
| import ( | |
| 	"fmt" | |
| 	"reflect" | |
| 	"strings" | |
| 	"time" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/admin/config" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/worker_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/worker/types" | |
| ) | |
| 
 | |
| // TaskDefinition encapsulates everything needed to define a complete task type | |
| type TaskDefinition struct { | |
| 	// Basic task information | |
| 	Type         types.TaskType | |
| 	Name         string | |
| 	DisplayName  string | |
| 	Description  string | |
| 	Icon         string | |
| 	Capabilities []string | |
| 
 | |
| 	// Task configuration | |
| 	Config     TaskConfig | |
| 	ConfigSpec ConfigSpec | |
| 
 | |
| 	// Task creation | |
| 	CreateTask func(params *worker_pb.TaskParams) (types.Task, error) | |
| 
 | |
| 	// Detection logic | |
| 	DetectionFunc func(metrics []*types.VolumeHealthMetrics, info *types.ClusterInfo, config TaskConfig) ([]*types.TaskDetectionResult, error) | |
| 	ScanInterval  time.Duration | |
| 
 | |
| 	// Scheduling logic | |
| 	SchedulingFunc func(task *types.TaskInput, running []*types.TaskInput, workers []*types.WorkerData, config TaskConfig) bool | |
| 	MaxConcurrent  int | |
| 	RepeatInterval time.Duration | |
| } | |
| 
 | |
| // TaskConfig provides a configuration interface that supports type-safe defaults | |
| type TaskConfig interface { | |
| 	config.ConfigWithDefaults // Extends ConfigWithDefaults for type-safe schema operations | |
| 	IsEnabled() bool | |
| 	SetEnabled(bool) | |
| 	ToTaskPolicy() *worker_pb.TaskPolicy | |
| 	FromTaskPolicy(policy *worker_pb.TaskPolicy) error | |
| } | |
| 
 | |
| // ConfigSpec defines the configuration schema | |
| type ConfigSpec struct { | |
| 	Fields []*config.Field | |
| } | |
| 
 | |
| // BaseConfig provides common configuration fields with reflection-based serialization | |
| type BaseConfig struct { | |
| 	Enabled             bool `json:"enabled"` | |
| 	ScanIntervalSeconds int  `json:"scan_interval_seconds"` | |
| 	MaxConcurrent       int  `json:"max_concurrent"` | |
| } | |
| 
 | |
| // IsEnabled returns whether the task is enabled | |
| func (c *BaseConfig) IsEnabled() bool { | |
| 	return c.Enabled | |
| } | |
| 
 | |
| // SetEnabled sets whether the task is enabled | |
| func (c *BaseConfig) SetEnabled(enabled bool) { | |
| 	c.Enabled = enabled | |
| } | |
| 
 | |
| // Validate validates the base configuration | |
| func (c *BaseConfig) Validate() error { | |
| 	// Common validation logic | |
| 	return nil | |
| } | |
| 
 | |
| // StructToMap converts any struct to a map using reflection | |
| func StructToMap(obj interface{}) map[string]interface{} { | |
| 	result := make(map[string]interface{}) | |
| 	val := reflect.ValueOf(obj) | |
| 
 | |
| 	// Handle pointer to struct | |
| 	if val.Kind() == reflect.Ptr { | |
| 		val = val.Elem() | |
| 	} | |
| 
 | |
| 	if val.Kind() != reflect.Struct { | |
| 		return result | |
| 	} | |
| 
 | |
| 	typ := val.Type() | |
| 
 | |
| 	for i := 0; i < val.NumField(); i++ { | |
| 		field := val.Field(i) | |
| 		fieldType := typ.Field(i) | |
| 
 | |
| 		// Skip unexported fields | |
| 		if !field.CanInterface() { | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Handle embedded structs recursively (before JSON tag check) | |
| 		if field.Kind() == reflect.Struct && fieldType.Anonymous { | |
| 			embeddedMap := StructToMap(field.Interface()) | |
| 			for k, v := range embeddedMap { | |
| 				result[k] = v | |
| 			} | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Get JSON tag name | |
| 		jsonTag := fieldType.Tag.Get("json") | |
| 		if jsonTag == "" || jsonTag == "-" { | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Remove options like ",omitempty" | |
| 		if commaIdx := strings.Index(jsonTag, ","); commaIdx >= 0 { | |
| 			jsonTag = jsonTag[:commaIdx] | |
| 		} | |
| 
 | |
| 		result[jsonTag] = field.Interface() | |
| 	} | |
| 	return result | |
| } | |
| 
 | |
| // MapToStruct loads data from map into struct using reflection | |
| func MapToStruct(data map[string]interface{}, obj interface{}) error { | |
| 	val := reflect.ValueOf(obj) | |
| 
 | |
| 	// Must be pointer to struct | |
| 	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct { | |
| 		return fmt.Errorf("obj must be pointer to struct") | |
| 	} | |
| 
 | |
| 	val = val.Elem() | |
| 	typ := val.Type() | |
| 
 | |
| 	for i := 0; i < val.NumField(); i++ { | |
| 		field := val.Field(i) | |
| 		fieldType := typ.Field(i) | |
| 
 | |
| 		// Skip unexported fields | |
| 		if !field.CanSet() { | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Handle embedded structs recursively (before JSON tag check) | |
| 		if field.Kind() == reflect.Struct && fieldType.Anonymous { | |
| 			err := MapToStruct(data, field.Addr().Interface()) | |
| 			if err != nil { | |
| 				return err | |
| 			} | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Get JSON tag name | |
| 		jsonTag := fieldType.Tag.Get("json") | |
| 		if jsonTag == "" || jsonTag == "-" { | |
| 			continue | |
| 		} | |
| 
 | |
| 		// Remove options like ",omitempty" | |
| 		if commaIdx := strings.Index(jsonTag, ","); commaIdx >= 0 { | |
| 			jsonTag = jsonTag[:commaIdx] | |
| 		} | |
| 
 | |
| 		if value, exists := data[jsonTag]; exists { | |
| 			err := setFieldValue(field, value) | |
| 			if err != nil { | |
| 				return fmt.Errorf("failed to set field %s: %v", jsonTag, err) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // ToMap converts config to map using reflection | |
| // ToTaskPolicy converts BaseConfig to protobuf (partial implementation) | |
| // Note: Concrete implementations should override this to include task-specific config | |
| func (c *BaseConfig) ToTaskPolicy() *worker_pb.TaskPolicy { | |
| 	return &worker_pb.TaskPolicy{ | |
| 		Enabled:               c.Enabled, | |
| 		MaxConcurrent:         int32(c.MaxConcurrent), | |
| 		RepeatIntervalSeconds: int32(c.ScanIntervalSeconds), | |
| 		CheckIntervalSeconds:  int32(c.ScanIntervalSeconds), | |
| 		// TaskConfig field should be set by concrete implementations | |
| 	} | |
| } | |
| 
 | |
| // FromTaskPolicy loads BaseConfig from protobuf (partial implementation) | |
| // Note: Concrete implementations should override this to handle task-specific config | |
| func (c *BaseConfig) FromTaskPolicy(policy *worker_pb.TaskPolicy) error { | |
| 	if policy == nil { | |
| 		return fmt.Errorf("policy is nil") | |
| 	} | |
| 	c.Enabled = policy.Enabled | |
| 	c.MaxConcurrent = int(policy.MaxConcurrent) | |
| 	c.ScanIntervalSeconds = int(policy.RepeatIntervalSeconds) | |
| 	return nil | |
| } | |
| 
 | |
| // ApplySchemaDefaults applies default values from schema using reflection | |
| func (c *BaseConfig) ApplySchemaDefaults(schema *config.Schema) error { | |
| 	// Use reflection-based approach for BaseConfig since it needs to handle embedded structs | |
| 	return schema.ApplyDefaultsToProtobuf(c) | |
| } | |
| 
 | |
| // setFieldValue sets a field value with type conversion | |
| func setFieldValue(field reflect.Value, value interface{}) error { | |
| 	if value == nil { | |
| 		return nil | |
| 	} | |
| 
 | |
| 	valueVal := reflect.ValueOf(value) | |
| 	fieldType := field.Type() | |
| 	valueType := valueVal.Type() | |
| 
 | |
| 	// Direct assignment if types match | |
| 	if valueType.AssignableTo(fieldType) { | |
| 		field.Set(valueVal) | |
| 		return nil | |
| 	} | |
| 
 | |
| 	// Type conversion for common cases | |
| 	switch fieldType.Kind() { | |
| 	case reflect.Bool: | |
| 		if b, ok := value.(bool); ok { | |
| 			field.SetBool(b) | |
| 		} else { | |
| 			return fmt.Errorf("cannot convert %T to bool", value) | |
| 		} | |
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 		switch v := value.(type) { | |
| 		case int: | |
| 			field.SetInt(int64(v)) | |
| 		case int32: | |
| 			field.SetInt(int64(v)) | |
| 		case int64: | |
| 			field.SetInt(v) | |
| 		case float64: | |
| 			field.SetInt(int64(v)) | |
| 		default: | |
| 			return fmt.Errorf("cannot convert %T to int", value) | |
| 		} | |
| 	case reflect.Float32, reflect.Float64: | |
| 		switch v := value.(type) { | |
| 		case float32: | |
| 			field.SetFloat(float64(v)) | |
| 		case float64: | |
| 			field.SetFloat(v) | |
| 		case int: | |
| 			field.SetFloat(float64(v)) | |
| 		case int64: | |
| 			field.SetFloat(float64(v)) | |
| 		default: | |
| 			return fmt.Errorf("cannot convert %T to float", value) | |
| 		} | |
| 	case reflect.String: | |
| 		if s, ok := value.(string); ok { | |
| 			field.SetString(s) | |
| 		} else { | |
| 			return fmt.Errorf("cannot convert %T to string", value) | |
| 		} | |
| 	default: | |
| 		return fmt.Errorf("unsupported field type %s", fieldType.Kind()) | |
| 	} | |
| 
 | |
| 	return nil | |
| }
 |