github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/cmd/lhsmd/agent/config.go (about) 1 // Copyright (c) 2018 DDN. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package agent 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "flag" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "path" 15 "runtime" 16 "strings" 17 18 "github.com/hashicorp/hcl" 19 "github.com/hashicorp/hcl/hcl/ast" 20 "github.com/pkg/errors" 21 22 "github.com/intel-hpdd/lemur/cmd/lhsmd/config" 23 "github.com/intel-hpdd/logging/alert" 24 "github.com/intel-hpdd/logging/debug" 25 "github.com/intel-hpdd/go-lustre/fs/spec" 26 ) 27 28 var ( 29 optConfigPath string 30 ) 31 32 type ( 33 transportConfig struct { 34 Type string `hcl:"type"` 35 SocketDir string `hcl:"socket_dir"` 36 } 37 38 influxConfig struct { 39 URL string `hcl:"url"` 40 DB string `hcl:"db"` 41 User string `hcl:"user"` 42 Password string `hcl:"password"` 43 } 44 45 snapshotConfig struct { 46 Enabled bool `hcl:"enabled"` 47 } 48 49 clientMountOptions []string 50 51 // Config represents HSM Agent configuration 52 Config struct { 53 MountRoot string `hcl:"mount_root" json:"mount_root"` 54 ClientDevice *spec.ClientDevice `json:"client_device"` 55 ClientMountOptions clientMountOptions `hcl:"client_mount_options" json:"client_mount_options"` 56 57 Processes int `hcl:"handler_count" json:"handler_count"` 58 59 InfluxDB *influxConfig `hcl:"influxdb" json:"influxdb"` 60 61 EnabledPlugins []string `hcl:"enabled_plugins" json:"enabled_plugins"` 62 PluginDir string `hcl:"plugin_dir" json:"plugin_dir"` 63 64 Snapshots *snapshotConfig `hcl:"snapshots" json:"snapshots"` 65 Transport *transportConfig `hcl:"transport" json:"transport"` 66 } 67 ) 68 69 func (cmo clientMountOptions) HasOption(o string) bool { 70 for _, option := range cmo { 71 if option == o { 72 return true 73 } 74 } 75 return false 76 } 77 78 func (cmo clientMountOptions) String() string { 79 return strings.Join(cmo, ",") 80 } 81 82 func (c *transportConfig) Merge(other *transportConfig) *transportConfig { 83 result := new(transportConfig) 84 85 result.Type = c.Type 86 if other.Type != "" { 87 result.Type = other.Type 88 } 89 90 result.SocketDir = c.SocketDir 91 if other.SocketDir != "" { 92 result.SocketDir = other.SocketDir 93 } 94 95 return result 96 } 97 98 func (c *transportConfig) ConnectionString() string { 99 return fmt.Sprintf("%s/lhsmd-%d", c.SocketDir, os.Getpid()) 100 } 101 102 func (c *influxConfig) Merge(other *influxConfig) *influxConfig { 103 result := new(influxConfig) 104 105 result.URL = c.URL 106 if other.URL != "" { 107 result.URL = other.URL 108 } 109 110 result.DB = c.DB 111 if other.DB != "" { 112 result.DB = other.DB 113 } 114 115 result.User = c.User 116 if other.User != "" { 117 result.User = other.User 118 } 119 120 result.Password = c.Password 121 if other.Password != "" { 122 result.Password = other.Password 123 } 124 125 return result 126 } 127 128 func (c *snapshotConfig) Merge(other *snapshotConfig) *snapshotConfig { 129 result := new(snapshotConfig) 130 131 result.Enabled = other.Enabled 132 133 return result 134 } 135 136 func init() { 137 flag.StringVar(&optConfigPath, "config", config.DefaultConfigPath, "Path to agent config") 138 139 // The CLI argument takes precedence, if both are set. 140 if optConfigPath == config.DefaultConfigPath { 141 if cfgDir := os.Getenv(config.ConfigDirEnvVar); cfgDir != "" { 142 optConfigPath = path.Join(cfgDir, config.AgentConfigFile) 143 } 144 } 145 } 146 147 func (c *Config) String() string { 148 data, err := json.Marshal(c) 149 if err != nil { 150 alert.Abort(errors.Wrap(err, "marshal failed")) 151 } 152 153 var out bytes.Buffer 154 json.Indent(&out, data, "", "\t") 155 return out.String() 156 } 157 158 // Plugins returns a slice of *PluginConfig instances for enabled plugins 159 func (c *Config) Plugins() []*PluginConfig { 160 var plugins []*PluginConfig 161 162 // Ensure that this is set in our env so that plugins can use it to 163 // find their own configs 164 os.Setenv(config.ConfigDirEnvVar, path.Dir(optConfigPath)) 165 166 connectAt := c.Transport.ConnectionString() 167 for _, name := range c.EnabledPlugins { 168 binPath := path.Join(c.PluginDir, name) 169 plugin := NewPlugin(name, binPath, connectAt, c.MountRoot) 170 plugins = append(plugins, plugin) 171 } 172 173 return plugins 174 } 175 176 // AgentMountpoint returns the calculated agent mountpoint under the 177 // agent mount root. 178 func (c *Config) AgentMountpoint() string { 179 return path.Join(c.MountRoot, "agent") 180 } 181 182 // Merge combines the supplied configuration's values with this one's 183 func (c *Config) Merge(other *Config) *Config { 184 result := new(Config) 185 186 result.MountRoot = c.MountRoot 187 if other.MountRoot != "" { 188 result.MountRoot = other.MountRoot 189 } 190 191 result.ClientDevice = c.ClientDevice 192 if other.ClientDevice != nil { 193 result.ClientDevice = other.ClientDevice 194 } 195 196 result.ClientMountOptions = c.ClientMountOptions 197 for _, otherOption := range other.ClientMountOptions { 198 if result.ClientMountOptions.HasOption(otherOption) { 199 continue 200 } 201 result.ClientMountOptions = append(result.ClientMountOptions, otherOption) 202 } 203 204 result.Processes = c.Processes 205 if other.Processes > result.Processes { 206 result.Processes = other.Processes 207 } 208 209 result.InfluxDB = c.InfluxDB 210 if other.InfluxDB != nil { 211 result.InfluxDB = result.InfluxDB.Merge(other.InfluxDB) 212 } 213 214 result.EnabledPlugins = c.EnabledPlugins 215 if len(other.EnabledPlugins) > 0 { 216 result.EnabledPlugins = other.EnabledPlugins 217 } 218 219 result.PluginDir = c.PluginDir 220 if other.PluginDir != "" { 221 result.PluginDir = other.PluginDir 222 } 223 224 result.Snapshots = c.Snapshots 225 if other.Snapshots != nil { 226 result.Snapshots = result.Snapshots.Merge(other.Snapshots) 227 } 228 229 result.Transport = c.Transport 230 if other.Transport != nil { 231 result.Transport = result.Transport.Merge(other.Transport) 232 } 233 234 return result 235 } 236 237 // DefaultConfig initializes a new Config struct with default values 238 func DefaultConfig() *Config { 239 cfg := NewConfig() 240 cfg.MountRoot = config.DefaultAgentMountRoot 241 cfg.ClientMountOptions = config.DefaultClientMountOptions 242 cfg.PluginDir = config.DefaultPluginDir 243 cfg.Processes = runtime.NumCPU() 244 cfg.Transport = &transportConfig{ 245 Type: config.DefaultTransport, 246 SocketDir: config.DefaultTransportSocketDir, 247 } 248 return cfg 249 } 250 251 // NewConfig initializes a new Config struct with zero values 252 func NewConfig() *Config { 253 return &Config{ 254 InfluxDB: &influxConfig{}, 255 Snapshots: &snapshotConfig{}, 256 Transport: &transportConfig{}, 257 EnabledPlugins: []string{}, 258 ClientMountOptions: clientMountOptions{}, 259 } 260 } 261 262 // LoadConfig reads a config at the supplied path 263 func LoadConfig(configPath string) (*Config, error) { 264 data, err := ioutil.ReadFile(configPath) 265 if err != nil { 266 return nil, errors.Wrap(err, "read failed") 267 } 268 269 obj, err := hcl.Parse(string(data)) 270 if err != nil { 271 return nil, errors.Wrap(err, "parse config failed") 272 } 273 274 defaults := DefaultConfig() 275 cfg := NewConfig() 276 if err = hcl.DecodeObject(cfg, obj); err != nil { 277 return nil, errors.Wrap(err, "decode config failed") 278 } 279 cfg = defaults.Merge(cfg) 280 281 list, ok := obj.Node.(*ast.ObjectList) 282 if !ok { 283 return nil, errors.Errorf("Malformed config file") 284 } 285 286 f := list.Filter("client_device") 287 if len(f.Items) == 0 { 288 return nil, errors.Errorf("No client_device specified") 289 } 290 if len(f.Items) > 1 { 291 return nil, errors.Errorf("Line %d: More than 1 client_device specified", f.Items[1].Assign.Line) 292 } 293 294 var devStr string 295 if err = hcl.DecodeObject(&devStr, f.Elem().Items[0].Val); err != nil { 296 return nil, errors.Wrap(err, "decode device failed") 297 } 298 cfg.ClientDevice, err = spec.ClientDeviceFromString(devStr) 299 if err != nil { 300 return nil, errors.Wrapf(err, "Line %d: Invalid client_device %q", f.Items[0].Assign.Line, devStr) 301 } 302 303 return cfg, nil 304 } 305 306 // ConfigInitMust returns a valid *Config or fails trying 307 func ConfigInitMust() *Config { 308 debug.Printf("loading config from %s", optConfigPath) 309 cfg, err := LoadConfig(optConfigPath) 310 if err != nil { 311 if !(optConfigPath == config.DefaultConfigPath && os.IsNotExist(err)) { 312 alert.Abort(errors.Wrap(err, "Failed to load config")) 313 } 314 } 315 316 if cfg.Transport == nil { 317 alert.Abort(errors.New("Invalid configuration: No transports configured")) 318 } 319 320 if _, err := os.Stat(cfg.PluginDir); os.IsNotExist(err) { 321 alert.Abort(errors.Errorf("Invalid configuration: plugin_dir %q does not exist", cfg.PluginDir)) 322 } 323 324 if len(cfg.EnabledPlugins) == 0 { 325 alert.Abort(errors.New("Invalid configuration: No data mover plugins configured")) 326 } 327 328 for _, plugin := range cfg.EnabledPlugins { 329 pluginPath := path.Join(cfg.PluginDir, plugin) 330 if _, err := os.Stat(pluginPath); os.IsNotExist(err) { 331 alert.Abort(errors.Errorf("Invalid configuration: Plugin %q not found in %s", plugin, cfg.PluginDir)) 332 } 333 } 334 335 return cfg 336 }