* add ui for maintenance * valid config loading. fix workers page. * refactor * grpc between admin and workers * add a long-running bidirectional grpc call between admin and worker * use the grpc call to heartbeat * use the grpc call to communicate * worker can remove the http client * admin uses http port + 10000 as its default grpc port * one task one package * handles connection failures gracefully with exponential backoff * grpc with insecure tls * grpc with optional tls * fix detecting tls * change time config from nano seconds to seconds * add tasks with 3 interfaces * compiles reducing hard coded * remove a couple of tasks * remove hard coded references * reduce hard coded values * remove hard coded values * remove hard coded from templ * refactor maintenance package * fix import cycle * simplify * simplify * auto register * auto register factory * auto register task types * self register types * refactor * simplify * remove one task * register ui * lazy init executor factories * use registered task types * DefaultWorkerConfig remove hard coded task types * remove more hard coded * implement get maintenance task * dynamic task configuration * "System Settings" should only have system level settings * adjust menu for tasks * ensure menu not collapsed * render job configuration well * use templ for ui of task configuration * fix ordering * fix bugs * saving duration in seconds * use value and unit for duration * Delete WORKER_REFACTORING_PLAN.md * Delete maintenance.json * Delete custom_worker_example.go * remove address from workers * remove old code from ec task * remove creating collection button * reconnect with exponential backoff * worker use security.toml * start admin server with tls info from security.toml * fix "weed admin" cli description
271 lines
7.8 KiB
Go
271 lines
7.8 KiB
Go
package dash
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
)
|
|
|
|
const (
|
|
// Configuration file names
|
|
MaintenanceConfigFile = "maintenance.json"
|
|
AdminConfigFile = "admin.json"
|
|
ConfigDirPermissions = 0755
|
|
ConfigFilePermissions = 0644
|
|
)
|
|
|
|
// ConfigPersistence handles saving and loading configuration files
|
|
type ConfigPersistence struct {
|
|
dataDir string
|
|
}
|
|
|
|
// NewConfigPersistence creates a new configuration persistence manager
|
|
func NewConfigPersistence(dataDir string) *ConfigPersistence {
|
|
return &ConfigPersistence{
|
|
dataDir: dataDir,
|
|
}
|
|
}
|
|
|
|
// SaveMaintenanceConfig saves maintenance configuration to JSON file
|
|
func (cp *ConfigPersistence) SaveMaintenanceConfig(config *MaintenanceConfig) error {
|
|
if cp.dataDir == "" {
|
|
return fmt.Errorf("no data directory specified, cannot save configuration")
|
|
}
|
|
|
|
configPath := filepath.Join(cp.dataDir, MaintenanceConfigFile)
|
|
|
|
// Create directory if it doesn't exist
|
|
if err := os.MkdirAll(cp.dataDir, ConfigDirPermissions); err != nil {
|
|
return fmt.Errorf("failed to create config directory: %v", err)
|
|
}
|
|
|
|
// Marshal configuration to JSON
|
|
configData, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal maintenance config: %v", err)
|
|
}
|
|
|
|
// Write to file
|
|
if err := os.WriteFile(configPath, configData, ConfigFilePermissions); err != nil {
|
|
return fmt.Errorf("failed to write maintenance config file: %v", err)
|
|
}
|
|
|
|
glog.V(1).Infof("Saved maintenance configuration to %s", configPath)
|
|
return nil
|
|
}
|
|
|
|
// LoadMaintenanceConfig loads maintenance configuration from JSON file
|
|
func (cp *ConfigPersistence) LoadMaintenanceConfig() (*MaintenanceConfig, error) {
|
|
if cp.dataDir == "" {
|
|
glog.V(1).Infof("No data directory specified, using default maintenance configuration")
|
|
return DefaultMaintenanceConfig(), nil
|
|
}
|
|
|
|
configPath := filepath.Join(cp.dataDir, MaintenanceConfigFile)
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
glog.V(1).Infof("Maintenance config file does not exist, using defaults: %s", configPath)
|
|
return DefaultMaintenanceConfig(), nil
|
|
}
|
|
|
|
// Read file
|
|
configData, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read maintenance config file: %v", err)
|
|
}
|
|
|
|
// Unmarshal JSON
|
|
var config MaintenanceConfig
|
|
if err := json.Unmarshal(configData, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal maintenance config: %v", err)
|
|
}
|
|
|
|
glog.V(1).Infof("Loaded maintenance configuration from %s", configPath)
|
|
return &config, nil
|
|
}
|
|
|
|
// SaveAdminConfig saves general admin configuration to JSON file
|
|
func (cp *ConfigPersistence) SaveAdminConfig(config map[string]interface{}) error {
|
|
if cp.dataDir == "" {
|
|
return fmt.Errorf("no data directory specified, cannot save configuration")
|
|
}
|
|
|
|
configPath := filepath.Join(cp.dataDir, AdminConfigFile)
|
|
|
|
// Create directory if it doesn't exist
|
|
if err := os.MkdirAll(cp.dataDir, ConfigDirPermissions); err != nil {
|
|
return fmt.Errorf("failed to create config directory: %v", err)
|
|
}
|
|
|
|
// Marshal configuration to JSON
|
|
configData, err := json.MarshalIndent(config, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal admin config: %v", err)
|
|
}
|
|
|
|
// Write to file
|
|
if err := os.WriteFile(configPath, configData, ConfigFilePermissions); err != nil {
|
|
return fmt.Errorf("failed to write admin config file: %v", err)
|
|
}
|
|
|
|
glog.V(1).Infof("Saved admin configuration to %s", configPath)
|
|
return nil
|
|
}
|
|
|
|
// LoadAdminConfig loads general admin configuration from JSON file
|
|
func (cp *ConfigPersistence) LoadAdminConfig() (map[string]interface{}, error) {
|
|
if cp.dataDir == "" {
|
|
glog.V(1).Infof("No data directory specified, using default admin configuration")
|
|
return make(map[string]interface{}), nil
|
|
}
|
|
|
|
configPath := filepath.Join(cp.dataDir, AdminConfigFile)
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
glog.V(1).Infof("Admin config file does not exist, using defaults: %s", configPath)
|
|
return make(map[string]interface{}), nil
|
|
}
|
|
|
|
// Read file
|
|
configData, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read admin config file: %v", err)
|
|
}
|
|
|
|
// Unmarshal JSON
|
|
var config map[string]interface{}
|
|
if err := json.Unmarshal(configData, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal admin config: %v", err)
|
|
}
|
|
|
|
glog.V(1).Infof("Loaded admin configuration from %s", configPath)
|
|
return config, nil
|
|
}
|
|
|
|
// GetConfigPath returns the path to a configuration file
|
|
func (cp *ConfigPersistence) GetConfigPath(filename string) string {
|
|
if cp.dataDir == "" {
|
|
return ""
|
|
}
|
|
return filepath.Join(cp.dataDir, filename)
|
|
}
|
|
|
|
// ListConfigFiles returns all configuration files in the data directory
|
|
func (cp *ConfigPersistence) ListConfigFiles() ([]string, error) {
|
|
if cp.dataDir == "" {
|
|
return nil, fmt.Errorf("no data directory specified")
|
|
}
|
|
|
|
files, err := os.ReadDir(cp.dataDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read config directory: %v", err)
|
|
}
|
|
|
|
var configFiles []string
|
|
for _, file := range files {
|
|
if !file.IsDir() && filepath.Ext(file.Name()) == ".json" {
|
|
configFiles = append(configFiles, file.Name())
|
|
}
|
|
}
|
|
|
|
return configFiles, nil
|
|
}
|
|
|
|
// BackupConfig creates a backup of a configuration file
|
|
func (cp *ConfigPersistence) BackupConfig(filename string) error {
|
|
if cp.dataDir == "" {
|
|
return fmt.Errorf("no data directory specified")
|
|
}
|
|
|
|
configPath := filepath.Join(cp.dataDir, filename)
|
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
|
return fmt.Errorf("config file does not exist: %s", filename)
|
|
}
|
|
|
|
// Create backup filename with timestamp
|
|
timestamp := time.Now().Format("2006-01-02_15-04-05")
|
|
backupName := fmt.Sprintf("%s.backup_%s", filename, timestamp)
|
|
backupPath := filepath.Join(cp.dataDir, backupName)
|
|
|
|
// Copy file
|
|
configData, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read config file: %v", err)
|
|
}
|
|
|
|
if err := os.WriteFile(backupPath, configData, ConfigFilePermissions); err != nil {
|
|
return fmt.Errorf("failed to create backup: %v", err)
|
|
}
|
|
|
|
glog.V(1).Infof("Created backup of %s as %s", filename, backupName)
|
|
return nil
|
|
}
|
|
|
|
// RestoreConfig restores a configuration file from a backup
|
|
func (cp *ConfigPersistence) RestoreConfig(filename, backupName string) error {
|
|
if cp.dataDir == "" {
|
|
return fmt.Errorf("no data directory specified")
|
|
}
|
|
|
|
backupPath := filepath.Join(cp.dataDir, backupName)
|
|
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
|
return fmt.Errorf("backup file does not exist: %s", backupName)
|
|
}
|
|
|
|
// Read backup file
|
|
backupData, err := os.ReadFile(backupPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read backup file: %v", err)
|
|
}
|
|
|
|
// Write to config file
|
|
configPath := filepath.Join(cp.dataDir, filename)
|
|
if err := os.WriteFile(configPath, backupData, ConfigFilePermissions); err != nil {
|
|
return fmt.Errorf("failed to restore config: %v", err)
|
|
}
|
|
|
|
glog.V(1).Infof("Restored %s from backup %s", filename, backupName)
|
|
return nil
|
|
}
|
|
|
|
// GetDataDir returns the data directory path
|
|
func (cp *ConfigPersistence) GetDataDir() string {
|
|
return cp.dataDir
|
|
}
|
|
|
|
// IsConfigured returns true if a data directory is configured
|
|
func (cp *ConfigPersistence) IsConfigured() bool {
|
|
return cp.dataDir != ""
|
|
}
|
|
|
|
// GetConfigInfo returns information about the configuration storage
|
|
func (cp *ConfigPersistence) GetConfigInfo() map[string]interface{} {
|
|
info := map[string]interface{}{
|
|
"data_dir_configured": cp.IsConfigured(),
|
|
"data_dir": cp.dataDir,
|
|
}
|
|
|
|
if cp.IsConfigured() {
|
|
// Check if data directory exists
|
|
if _, err := os.Stat(cp.dataDir); err == nil {
|
|
info["data_dir_exists"] = true
|
|
|
|
// List config files
|
|
configFiles, err := cp.ListConfigFiles()
|
|
if err == nil {
|
|
info["config_files"] = configFiles
|
|
}
|
|
} else {
|
|
info["data_dir_exists"] = false
|
|
}
|
|
}
|
|
|
|
return info
|
|
}
|