open-nomad/command/agent/config.go

572 lines
14 KiB
Go
Raw Normal View History

2015-08-16 01:54:41 +00:00
package agent
import (
"fmt"
"io"
"io/ioutil"
"net"
2015-08-16 01:54:41 +00:00
"os"
"path/filepath"
"strings"
"github.com/hashicorp/hcl"
hclobj "github.com/hashicorp/hcl/hcl"
2015-09-06 01:41:00 +00:00
client "github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/nomad"
2015-08-16 01:54:41 +00:00
)
// Config is the configuration for the Nomad agent.
type Config struct {
// Region is the region this agent is in. Defaults to global.
2015-08-16 22:10:11 +00:00
Region string `hcl:"region"`
2015-08-16 01:54:41 +00:00
2015-08-31 01:10:23 +00:00
// Datacenter is the datacenter this agent is in. Defaults to dc1
Datacenter string `hcl:"datacenter"`
// NodeName is the name we register as. Defaults to hostname.
NodeName string `hcl:"name"`
2015-08-16 01:54:41 +00:00
// DataDir is the directory to store our state in
2015-08-16 22:10:11 +00:00
DataDir string `hcl:"data_dir"`
2015-08-16 01:54:41 +00:00
// LogLevel is the level of the logs to putout
2015-08-16 22:10:11 +00:00
LogLevel string `hcl:"log_level"`
2015-08-16 01:54:41 +00:00
// BindAddr is the address on which all of nomad's services will
// be bound. If not specified, this defaults to 127.0.0.1.
BindAddr string `hcl:"bind_addr"`
2015-08-24 00:50:33 +00:00
// EnableDebug is used to enable debugging HTTP endpoints
EnableDebug bool `hcl:"enable_debug"`
// Ports is used to control the network ports we bind to.
Ports *Ports `hcl:"ports"`
// Addresses is used to override the network addresses we bind to.
Addresses *Addresses `hcl:"addresses"`
// AdvertiseAddrs is used to control the addresses we advertise.
AdvertiseAddrs *AdvertiseAddrs `hcl:"advertise"`
2015-08-16 01:54:41 +00:00
// Client has our client related settings
Client *ClientConfig `hcl:"client"`
// Server has our server related settings
Server *ServerConfig `hcl:"server"`
Telemetry *Telemetry `hcl:"telemetry"`
2015-08-16 20:54:49 +00:00
LeaveOnInt bool
LeaveOnTerm bool
EnableSyslog bool
SyslogFacility string
2015-08-16 21:34:38 +00:00
DisableUpdateCheck bool
DisableAnonymousSignature bool
2015-08-16 22:10:11 +00:00
Revision string
2015-08-16 21:34:38 +00:00
Version string
VersionPrerelease string
2015-08-24 00:40:27 +00:00
DevMode bool `hcl:"-"`
2015-09-06 01:41:00 +00:00
2015-09-14 22:33:08 +00:00
// AtlasConfig is used to configure Atlas
Atlas *AtlasConfig `hcl:"atlas"`
2015-09-06 01:41:00 +00:00
// NomadConfig is used to override the default config.
// This is largly used for testing purposes.
NomadConfig *nomad.Config `hcl:"-" json:"-"`
// ClientConfig is used to override the default config.
// This is largly used for testing purposes.
ClientConfig *client.Config `hcl:"-" json:"-"`
2015-08-16 01:54:41 +00:00
}
2015-09-14 22:33:08 +00:00
// AtlasConfig is used to enable an parameterize the Atlas integration
type AtlasConfig struct {
// Infrastructure is the name of the infrastructure we belong to. e.g. hashicorp/stage
Infrastructure string `hcl:"infrastructure"`
// Token is our authentication token from Atlas
Token string `hcl:"token" json:"-"`
// Join controls if Atlas will attempt to auto-join the node
// to it's cluster. Requires Atlas integration.
Join bool `hcl:"join"`
// Endpoint is the SCADA endpoint used for Atlas integration. If
// empty, the defaults from the provider are used.
Endpoint string `hcl:"endpoint"`
}
2015-08-16 01:54:41 +00:00
type ClientConfig struct {
// Enabled controls if we are a client
Enabled bool `hcl:"enabled"`
2015-08-31 01:10:23 +00:00
// StateDir is the state directory
StateDir string `hcl:"state_dir"`
2015-08-16 01:54:41 +00:00
2015-08-31 01:10:23 +00:00
// AllocDir is the directory for storing allocation data
AllocDir string `hcl:"alloc_dir"`
// Servers is a list of known server addresses. These are as "host:port"
Servers []string `hcl:"servers"`
2015-08-16 01:54:41 +00:00
// NodeID is the unique node identifier to use. A UUID is used
// if not provided, and stored in the data directory
NodeID string `hcl:"node_id"`
2015-08-31 01:10:23 +00:00
// NodeClass is used to group the node by class
NodeClass string `hcl:"node_class"`
// Metadata associated with the node
Meta map[string]string `hcl:"meta"`
2015-08-16 01:54:41 +00:00
}
type ServerConfig struct {
// Enabled controls if we are a server
Enabled bool `hcl:"enabled"`
// Bootstrap is used to bring up the first Consul server, and
// permits that node to elect itself leader
Bootstrap bool `hcl:"bootstrap"`
// BootstrapExpect tries to automatically bootstrap the Consul cluster,
// by witholding peers until enough servers join.
BootstrapExpect int `hcl:"bootstrap_expect"`
2015-08-31 01:10:23 +00:00
// DataDir is the directory to store our state in
DataDir string `hcl:"data_dir"`
// ProtocolVersion is the protocol version to speak. This must be between
// ProtocolVersionMin and ProtocolVersionMax.
ProtocolVersion int `hcl:"protocol_version"`
// NumSchedulers is the number of scheduler thread that are run.
// This can be as many as one per core, or zero to disable this server
// from doing any scheduling work.
NumSchedulers int `hcl:"num_schedulers"`
// EnabledSchedulers controls the set of sub-schedulers that are
// enabled for this server to handle. This will restrict the evaluations
// that the workers dequeue for processing.
EnabledSchedulers []string `hcl:"enabled_schedulers"`
2015-08-16 01:54:41 +00:00
}
// Telemetry is the telemetry configuration for the server
type Telemetry struct {
StatsiteAddr string `hcl:"statsite_address"`
StatsdAddr string `hcl:"statsd_address"`
DisableHostname bool `hcl:"disable_hostname"`
}
// Ports is used to encapsulate the various ports we bind to for network
// services. If any are not specified then the defaults are used instead.
type Ports struct {
HTTP int `hcl:"http"`
RPC int `hcl:"rpc"`
Serf int `hcl:"serf"`
}
// Addresses encapsulates all of the addresses we bind to for various
// network services. Everything is optional and defaults to BindAddr.
type Addresses struct {
HTTP string `hcl:"http"`
RPC string `hcl:"rpc"`
Serf string `hcl:"serf"`
}
// AdvertiseAddrs is used to control the addresses we advertise out for
// different network services. Not all network services support an
// advertise address. All are optional and default to BindAddr.
type AdvertiseAddrs struct {
RPC string `hcl:"rpc"`
Serf string `hcl:"serf"`
}
2015-08-16 01:54:41 +00:00
// DevConfig is a Config that is used for dev mode of Nomad.
func DevConfig() *Config {
2015-09-06 00:06:05 +00:00
conf := DefaultConfig()
conf.LogLevel = "DEBUG"
conf.Client.Enabled = true
conf.Server.Enabled = true
conf.DevMode = true
conf.EnableDebug = true
conf.DisableAnonymousSignature = true
return conf
2015-08-16 20:54:49 +00:00
}
// DefaultConfig is a the baseline configuration for Nomad
func DefaultConfig() *Config {
return &Config{
LogLevel: "INFO",
Region: "global",
Datacenter: "dc1",
BindAddr: "127.0.0.1",
Ports: &Ports{
HTTP: 4646,
RPC: 4647,
Serf: 4648,
},
Addresses: &Addresses{},
AdvertiseAddrs: &AdvertiseAddrs{},
2015-09-06 00:06:05 +00:00
Client: &ClientConfig{
Enabled: false,
},
Server: &ServerConfig{
Enabled: false,
},
2015-08-16 20:54:49 +00:00
}
2015-08-16 01:54:41 +00:00
}
// GetListener can be used to get a new listener using a custom bind address.
// If the bind provided address is empty, the BindAddr is used instead.
func (c *Config) Listener(proto, addr string, port int) (net.Listener, error) {
if addr == "" {
addr = c.BindAddr
}
return net.Listen(proto, fmt.Sprintf("%s:%d", addr, port))
}
2015-08-16 01:54:41 +00:00
// Merge merges two configurations.
2015-09-10 04:42:50 +00:00
func (a *Config) Merge(b *Config) *Config {
var result Config = *a
2015-08-16 01:54:41 +00:00
2015-09-10 04:42:50 +00:00
if b.Region != "" {
result.Region = b.Region
2015-08-16 01:54:41 +00:00
}
2015-09-10 04:42:50 +00:00
if b.Datacenter != "" {
result.Datacenter = b.Datacenter
}
if b.NodeName != "" {
result.NodeName = b.NodeName
}
if b.DataDir != "" {
result.DataDir = b.DataDir
}
if b.LogLevel != "" {
result.LogLevel = b.LogLevel
}
if b.BindAddr != "" {
result.BindAddr = b.BindAddr
}
2015-09-10 04:42:50 +00:00
if b.EnableDebug {
result.EnableDebug = true
}
if b.LeaveOnInt {
result.LeaveOnInt = true
}
if b.LeaveOnTerm {
result.LeaveOnTerm = true
}
if b.EnableSyslog {
result.EnableSyslog = true
}
if b.SyslogFacility != "" {
result.SyslogFacility = b.SyslogFacility
}
if b.DisableUpdateCheck {
result.DisableUpdateCheck = true
}
if b.DisableAnonymousSignature {
result.DisableAnonymousSignature = true
}
// Apply the telemetry config
if result.Telemetry == nil && b.Telemetry != nil {
telemetry := *b.Telemetry
result.Telemetry = &telemetry
} else if b.Telemetry != nil {
result.Telemetry = result.Telemetry.Merge(b.Telemetry)
}
// Apply the client config
if result.Client == nil && b.Client != nil {
client := *b.Client
result.Client = &client
} else if b.Client != nil {
result.Client = result.Client.Merge(b.Client)
}
// Apply the server config
if result.Server == nil && b.Server != nil {
server := *b.Server
result.Server = &server
} else if b.Server != nil {
result.Server = result.Server.Merge(b.Server)
}
// Apply the ports config
if result.Ports == nil && b.Ports != nil {
ports := *b.Ports
result.Ports = &ports
} else if b.Ports != nil {
result.Ports = result.Ports.Merge(b.Ports)
}
// Apply the address config
if result.Addresses == nil && b.Addresses != nil {
addrs := *b.Addresses
result.Addresses = &addrs
} else if b.Addresses != nil {
result.Addresses = result.Addresses.Merge(b.Addresses)
}
// Apply the advertise addrs config
if result.AdvertiseAddrs == nil && b.AdvertiseAddrs != nil {
advertise := *b.AdvertiseAddrs
result.AdvertiseAddrs = &advertise
} else if b.AdvertiseAddrs != nil {
result.AdvertiseAddrs = result.AdvertiseAddrs.Merge(b.AdvertiseAddrs)
}
return &result
}
// Merge is used to merge two server configs together
func (a *ServerConfig) Merge(b *ServerConfig) *ServerConfig {
var result ServerConfig = *a
if b.Enabled {
result.Enabled = true
}
if b.Bootstrap {
result.Bootstrap = true
}
if b.BootstrapExpect > 0 {
result.BootstrapExpect = b.BootstrapExpect
}
if b.DataDir != "" {
result.DataDir = b.DataDir
}
if b.ProtocolVersion != 0 {
result.ProtocolVersion = b.ProtocolVersion
}
if b.NumSchedulers != 0 {
result.NumSchedulers = b.NumSchedulers
}
// Add the schedulers
result.EnabledSchedulers = append(result.EnabledSchedulers, b.EnabledSchedulers...)
return &result
}
// Merge is used to merge two client configs together
func (a *ClientConfig) Merge(b *ClientConfig) *ClientConfig {
var result ClientConfig = *a
if b.Enabled {
result.Enabled = true
}
if b.StateDir != "" {
result.StateDir = b.StateDir
}
if b.AllocDir != "" {
result.AllocDir = b.AllocDir
}
if b.NodeID != "" {
result.NodeID = b.NodeID
}
if b.NodeClass != "" {
result.NodeClass = b.NodeClass
}
// Add the servers
result.Servers = append(result.Servers, b.Servers...)
// Add the meta map values
if result.Meta == nil {
result.Meta = make(map[string]string)
}
for k, v := range b.Meta {
result.Meta[k] = v
}
return &result
}
// Merge is used to merge two telemetry configs together
func (a *Telemetry) Merge(b *Telemetry) *Telemetry {
var result Telemetry = *a
if b.StatsiteAddr != "" {
result.StatsiteAddr = b.StatsiteAddr
}
if b.StatsdAddr != "" {
result.StatsdAddr = b.StatsdAddr
}
if b.DisableHostname {
result.DisableHostname = true
}
2015-09-10 04:42:50 +00:00
return &result
2015-08-16 01:54:41 +00:00
}
// Merge is used to merge two port configurations.
func (a *Ports) Merge(b *Ports) *Ports {
var result Ports = *a
if b.HTTP != 0 {
result.HTTP = b.HTTP
}
if b.RPC != 0 {
result.RPC = b.RPC
}
if b.Serf != 0 {
result.Serf = b.Serf
}
return &result
}
// Merge is used to merge two address configs together.
func (a *Addresses) Merge(b *Addresses) *Addresses {
var result Addresses = *a
if b.HTTP != "" {
result.HTTP = b.HTTP
}
if b.RPC != "" {
result.RPC = b.RPC
}
if b.Serf != "" {
result.Serf = b.Serf
}
return &result
}
// Merge merges two advertise addrs configs together.
func (a *AdvertiseAddrs) Merge(b *AdvertiseAddrs) *AdvertiseAddrs {
var result AdvertiseAddrs = *a
if b.RPC != "" {
result.RPC = b.RPC
}
if b.Serf != "" {
result.Serf = b.Serf
}
return &result
}
2015-08-16 01:54:41 +00:00
// LoadConfig loads the configuration at the given path, regardless if
// its a file or directory.
func LoadConfig(path string) (*Config, error) {
fi, err := os.Stat(path)
if err != nil {
return nil, err
}
if fi.IsDir() {
return LoadConfigDir(path)
} else {
return LoadConfigFile(path)
}
}
// LoadConfigFile loads the configuration from the given file.
func LoadConfigFile(path string) (*Config, error) {
// Read the file
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
// Parse!
obj, err := hcl.Parse(string(d))
if err != nil {
return nil, err
}
// Start building the result
var result Config
if err := hcl.DecodeObject(&result, obj); err != nil {
return nil, err
}
return &result, nil
}
func getString(o *hclobj.Object) string {
if o == nil || o.Type != hclobj.ValueTypeString {
return ""
}
return o.Value.(string)
}
// LoadConfigDir loads all the configurations in the given directory
// in alphabetical order.
func LoadConfigDir(dir string) (*Config, error) {
f, err := os.Open(dir)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
if !fi.IsDir() {
return nil, fmt.Errorf(
"configuration path must be a directory: %s",
dir)
}
var files []string
err = nil
for err != io.EOF {
var fis []os.FileInfo
fis, err = f.Readdir(128)
if err != nil && err != io.EOF {
return nil, err
}
for _, fi := range fis {
// Ignore directories
if fi.IsDir() {
continue
}
// Only care about files that are valid to load.
name := fi.Name()
skip := true
if strings.HasSuffix(name, ".hcl") {
skip = false
} else if strings.HasSuffix(name, ".json") {
skip = false
}
if skip || isTemporaryFile(name) {
continue
}
path := filepath.Join(dir, name)
files = append(files, path)
}
}
var result *Config
for _, f := range files {
config, err := LoadConfigFile(f)
if err != nil {
return nil, fmt.Errorf("Error loading %s: %s", f, err)
}
if result == nil {
result = config
} else {
result = result.Merge(config)
}
}
return result, nil
}
// isTemporaryFile returns true or false depending on whether the
// provided file name is a temporary file for the following editors:
// emacs or vim.
func isTemporaryFile(name string) bool {
return strings.HasSuffix(name, "~") || // vim
strings.HasPrefix(name, ".#") || // emacs
(strings.HasPrefix(name, "#") && strings.HasSuffix(name, "#")) // emacs
}