github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/controller/config.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  	"regexp"
    10  	"time"
    11  
    12  	"github.com/juju/collections/set"
    13  	"github.com/juju/errors"
    14  	"github.com/juju/romulus"
    15  	"github.com/juju/schema"
    16  	"github.com/juju/utils"
    17  	utilscert "github.com/juju/utils/cert"
    18  	"gopkg.in/juju/charmrepo.v3/csclient"
    19  	"gopkg.in/juju/names.v2"
    20  	"gopkg.in/macaroon-bakery.v2-unstable/bakery"
    21  
    22  	"github.com/juju/juju/cert"
    23  	"github.com/juju/juju/core/resources"
    24  )
    25  
    26  const (
    27  	// MongoProfLow represents the most conservative mongo memory profile.
    28  	MongoProfLow = "low"
    29  	// MongoProfDefault represents the mongo memory profile shipped by default.
    30  	MongoProfDefault = "default"
    31  )
    32  
    33  const (
    34  	// APIPort is the port used for api connections.
    35  	APIPort = "api-port"
    36  
    37  	// ControllerAPIPort is an optional port that may be set for controllers
    38  	// that have a very heavy load. If this port is set, this port is used by
    39  	// the controllers to talk to each other - used for the local API connection
    40  	// as well as the pubsub forwarders, and the raft workers. If this value is
    41  	// set, the api-port isn't opened until the controllers have started
    42  	// properly.
    43  	ControllerAPIPort = "controller-api-port"
    44  
    45  	// APIPortOpenDelay is a duration that the controller will wait
    46  	// between when the controller has been deemed to be ready to open
    47  	// the api-port and when the api-port is actually opened. This value
    48  	// is only used when a controller-api-port value is set.
    49  	APIPortOpenDelay = "api-port-open-delay"
    50  
    51  	// AuditingEnabled determines whether the controller will record
    52  	// auditing information.
    53  	AuditingEnabled = "auditing-enabled"
    54  
    55  	// AuditLogCaptureArgs determines whether the audit log will
    56  	// contain the arguments passed to API methods.
    57  	AuditLogCaptureArgs = "audit-log-capture-args"
    58  
    59  	// AuditLogMaxSize is the maximum size for the current audit log
    60  	// file, eg "250M".
    61  	AuditLogMaxSize = "audit-log-max-size"
    62  
    63  	// AuditLogMaxBackups is the number of old audit log files to keep
    64  	// (compressed).
    65  	AuditLogMaxBackups = "audit-log-max-backups"
    66  
    67  	// AuditLogExcludeMethods is a list of Facade.Method names that
    68  	// aren't interesting for audit logging purposes. A conversation
    69  	// with only calls to these will be excluded from the
    70  	// log. (They'll still appear in conversations that have other
    71  	// interesting calls though.)
    72  	AuditLogExcludeMethods = "audit-log-exclude-methods"
    73  
    74  	// ReadOnlyMethodsWildcard is the special value that can be added
    75  	// to the exclude-methods list that represents all of the read
    76  	// only methods (see apiserver/observer/auditfilter.go). This
    77  	// value will be stored in the DB (rather than being expanded at
    78  	// write time) so any changes to the set of read-only methods in
    79  	// new versions of Juju will be honoured.
    80  	ReadOnlyMethodsWildcard = "ReadOnlyMethods"
    81  
    82  	// StatePort is the port used for mongo connections.
    83  	StatePort = "state-port"
    84  
    85  	// CACertKey is the key for the controller's CA certificate attribute.
    86  	CACertKey = "ca-cert"
    87  
    88  	// CharmStoreURL is the key for the url to use for charmstore API calls
    89  	CharmStoreURL = "charmstore-url"
    90  
    91  	// ControllerUUIDKey is the key for the controller UUID attribute.
    92  	ControllerUUIDKey = "controller-uuid"
    93  
    94  	// IdentityURL sets the url of the identity manager.
    95  	IdentityURL = "identity-url"
    96  
    97  	// IdentityPublicKey sets the public key of the identity manager.
    98  	IdentityPublicKey = "identity-public-key"
    99  
   100  	// SetNUMAControlPolicyKey stores the value for this setting
   101  	SetNUMAControlPolicyKey = "set-numa-control-policy"
   102  
   103  	// AutocertDNSNameKey sets the DNS name of the controller. If a
   104  	// client connects to this name, an official certificate will be
   105  	// automatically requested. Connecting to any other host name
   106  	// will use the usual self-generated certificate.
   107  	AutocertDNSNameKey = "autocert-dns-name"
   108  
   109  	// AutocertURLKey sets the URL used to obtain official TLS
   110  	// certificates when a client connects to the API. By default,
   111  	// certficates are obtains from LetsEncrypt. A good value for
   112  	// testing is
   113  	// "https://acme-staging.api.letsencrypt.org/directory".
   114  	AutocertURLKey = "autocert-url"
   115  
   116  	// AllowModelAccessKey sets whether the controller will allow users to
   117  	// connect to models they have been authorized for even when
   118  	// they don't have any access rights to the controller itself.
   119  	AllowModelAccessKey = "allow-model-access"
   120  
   121  	// MongoMemoryProfile sets whether mongo uses the least possible memory or the
   122  	// detault
   123  	MongoMemoryProfile = "mongo-memory-profile"
   124  
   125  	// MaxLogsAge is the maximum age for log entries, eg "72h"
   126  	MaxLogsAge = "max-logs-age"
   127  
   128  	// MaxLogsSize is the maximum size the log collection can grow to
   129  	// before it is pruned, eg "4M"
   130  	MaxLogsSize = "max-logs-size"
   131  
   132  	// MaxTxnLogSize is the maximum size the of capped txn log collection, eg "10M"
   133  	MaxTxnLogSize = "max-txn-log-size"
   134  
   135  	// MaxPruneTxnBatchSize (deprecated) is the maximum number of transactions
   136  	// we will evaluate in one go when pruning. Default is 1M transactions.
   137  	// A value <= 0 indicates to do all transactions at once.
   138  	MaxPruneTxnBatchSize = "max-prune-txn-batch-size"
   139  
   140  	// MaxPruneTxnPasses (deprecated) is the maximum number of batches that we will process.
   141  	// So total number of transactions that can be processed is MaxPruneTxnBatchSize * MaxPruneTxnPasses.
   142  	// A value <= 0 implies 'do a single pass'. If both MaxPruneTxnBatchSize and MaxPruneTxnPasses are 0, then the
   143  	// default value of 1M BatchSize and 100 passes will be used instead.
   144  	MaxPruneTxnPasses = "max-prune-txn-passes"
   145  
   146  	// PruneTxnQueryCount is the number of transactions to read in a single query.
   147  	// Minimum of 10, a value of 0 will indicate to use the default value (1000)
   148  	PruneTxnQueryCount = "prune-txn-query-count"
   149  
   150  	// PruneTxnSleepTime is the amount of time to sleep between processing each
   151  	// batch query. This is used to reduce load on the system, allowing other queries
   152  	// to time to operate. On large controllers, processing 1000 txs seems to take
   153  	// about 100ms, so a sleep time of 10ms represents a 10% slowdown, but allows
   154  	// other systems to operate concurrently.
   155  	// A negative number will indicate to use the default, a value of 0 indicates
   156  	// to not sleep at all.
   157  	PruneTxnSleepTime = "prune-txn-sleep-time"
   158  
   159  	// Attribute Defaults
   160  
   161  	// DefaultAuditingEnabled contains the default value for the
   162  	// AuditingEnabled config value.
   163  	DefaultAuditingEnabled = true
   164  
   165  	// DefaultAuditLogCaptureArgs is the default for the
   166  	// AuditLogCaptureArgs setting (which is not to capture them).
   167  	DefaultAuditLogCaptureArgs = false
   168  
   169  	// DefaultAuditLogMaxSizeMB is the default size in MB at which we
   170  	// roll the audit log file.
   171  	DefaultAuditLogMaxSizeMB = 300
   172  
   173  	// DefaultAuditLogMaxBackups is the default number of files to
   174  	// keep.
   175  	DefaultAuditLogMaxBackups = 10
   176  
   177  	// DefaultNUMAControlPolicy should not be used by default.
   178  	// Only use numactl if user specifically requests it
   179  	DefaultNUMAControlPolicy = false
   180  
   181  	// DefaultStatePort is the default port the controller is listening on.
   182  	DefaultStatePort int = 37017
   183  
   184  	// DefaultAPIPort is the default port the API server is listening on.
   185  	DefaultAPIPort int = 17070
   186  
   187  	// DefaultAPIPortOpenDelay is the default value for api-port-open-delay.
   188  	// It is a string representation of a time.Duration.
   189  	DefaultAPIPortOpenDelay = "2s"
   190  
   191  	// DefaultMongoMemoryProfile is the default profile used by mongo.
   192  	DefaultMongoMemoryProfile = MongoProfLow
   193  
   194  	// DefaultMaxLogsAgeDays is the maximum age in days of log entries.
   195  	DefaultMaxLogsAgeDays = 3
   196  
   197  	// DefaultMaxLogCollectionMB is the maximum size the log collection can
   198  	// grow to before being pruned.
   199  	DefaultMaxLogCollectionMB = 4 * 1024 // 4 GB
   200  
   201  	// DefaultMaxTxnLogCollectionMB is the maximum size the txn log collection.
   202  	DefaultMaxTxnLogCollectionMB = 10 // 10 MB
   203  
   204  	// DefaultMaxPruneTxnBatchSize is the normal number of transaction we will prune in a given pass (1M) (deprecated)
   205  	DefaultMaxPruneTxnBatchSize = 1 * 1000 * 1000
   206  
   207  	// DefaultMaxPruneTxnPasses is the default number of batches we will process (deprecated)
   208  	DefaultMaxPruneTxnPasses = 100
   209  
   210  	// DefaultPruneTxnQueryCount is the number of transactions to read in a single query.
   211  	DefaultPruneTxnQueryCount = 1000
   212  
   213  	// DefaultPruneTxnSleepTime is the amount of time to sleep between processing each
   214  	// batch query. This is used to reduce load on the system, allowing other queries
   215  	// to time to operate. On large controllers, processing 1000 txs seems to take
   216  	// about 100ms, so a sleep time of 10ms represents a 10% slowdown, but allows
   217  	// other systems to operate concurrently.
   218  	DefaultPruneTxnSleepTime = "10ms"
   219  
   220  	// JujuHASpace is the network space within which the MongoDB replica-set
   221  	// should communicate.
   222  	JujuHASpace = "juju-ha-space"
   223  
   224  	// JujuManagementSpace is the network space that agents should use to
   225  	// communicate with controllers.
   226  	JujuManagementSpace = "juju-mgmt-space"
   227  
   228  	// CAASOperatorImagePath sets the url of the docker image
   229  	// used for the application operator.
   230  	CAASOperatorImagePath = "caas-operator-image-path"
   231  
   232  	// Features allows a list of runtime changeable features to be updated.
   233  	Features = "features"
   234  
   235  	// MeteringURL is the key for the url to use for metrics
   236  	MeteringURL = "metering-url"
   237  )
   238  
   239  var (
   240  	// ControllerOnlyConfigAttributes are attributes which are only relevant
   241  	// for a controller, never a model.
   242  	ControllerOnlyConfigAttributes = []string{
   243  		AllowModelAccessKey,
   244  		APIPort,
   245  		APIPortOpenDelay,
   246  		AutocertDNSNameKey,
   247  		AutocertURLKey,
   248  		CACertKey,
   249  		CharmStoreURL,
   250  		ControllerAPIPort,
   251  		ControllerUUIDKey,
   252  		IdentityPublicKey,
   253  		IdentityURL,
   254  		SetNUMAControlPolicyKey,
   255  		StatePort,
   256  		MongoMemoryProfile,
   257  		MaxLogsSize,
   258  		MaxLogsAge,
   259  		MaxTxnLogSize,
   260  		MaxPruneTxnBatchSize,
   261  		MaxPruneTxnPasses,
   262  		PruneTxnQueryCount,
   263  		PruneTxnSleepTime,
   264  		JujuHASpace,
   265  		JujuManagementSpace,
   266  		AuditingEnabled,
   267  		AuditLogCaptureArgs,
   268  		AuditLogMaxSize,
   269  		AuditLogMaxBackups,
   270  		AuditLogExcludeMethods,
   271  		CAASOperatorImagePath,
   272  		Features,
   273  		MeteringURL,
   274  	}
   275  
   276  	// AllowedUpdateConfigAttributes contains all of the controller
   277  	// config attributes that are allowed to be updated after the
   278  	// controller has been created.
   279  	AllowedUpdateConfigAttributes = set.NewStrings(
   280  		APIPortOpenDelay,
   281  		AuditingEnabled,
   282  		AuditLogCaptureArgs,
   283  		AuditLogExcludeMethods,
   284  		// TODO Juju 3.0: ControllerAPIPort should be required and treated
   285  		// more like api-port.
   286  		ControllerAPIPort,
   287  		MaxPruneTxnBatchSize,
   288  		MaxPruneTxnPasses,
   289  		MaxLogsSize,
   290  		MaxLogsAge,
   291  		PruneTxnQueryCount,
   292  		PruneTxnSleepTime,
   293  		JujuHASpace,
   294  		JujuManagementSpace,
   295  		CAASOperatorImagePath,
   296  		Features,
   297  	)
   298  
   299  	// DefaultAuditLogExcludeMethods is the default list of methods to
   300  	// exclude from the audit log.
   301  	DefaultAuditLogExcludeMethods = []string{
   302  		// This special value means we exclude any methods in the set
   303  		// listed in apiserver/observer/auditfilter.go
   304  		ReadOnlyMethodsWildcard,
   305  	}
   306  
   307  	methodNameRE = regexp.MustCompile(`[[:alpha:]][[:alnum:]]*\.[[:alpha:]][[:alnum:]]*`)
   308  )
   309  
   310  // ControllerOnlyAttribute returns true if the specified attribute name
   311  // is only relevant for a controller.
   312  func ControllerOnlyAttribute(attr string) bool {
   313  	for _, a := range ControllerOnlyConfigAttributes {
   314  		if attr == a {
   315  			return true
   316  		}
   317  	}
   318  	return false
   319  }
   320  
   321  // Config is a string-keyed map of controller configuration attributes.
   322  type Config map[string]interface{}
   323  
   324  // Validate validates the controller configuration.
   325  func (c Config) Validate() error {
   326  	return Validate(c)
   327  }
   328  
   329  // NewConfig creates a new Config from the supplied attributes.
   330  // Default values will be used where defaults are available.
   331  //
   332  // The controller UUID and CA certificate must be passed in.
   333  // The UUID is typically generated by the immediate caller,
   334  // and the CA certificate generated by environs/bootstrap.NewConfig.
   335  func NewConfig(controllerUUID, caCert string, attrs map[string]interface{}) (Config, error) {
   336  	coerced, err := configChecker.Coerce(attrs, nil)
   337  	if err != nil {
   338  		return Config{}, errors.Trace(err)
   339  	}
   340  	attrs = coerced.(map[string]interface{})
   341  	attrs[ControllerUUIDKey] = controllerUUID
   342  	attrs[CACertKey] = caCert
   343  	config := Config(attrs)
   344  	return config, config.Validate()
   345  }
   346  
   347  // mustInt returns the named attribute as an integer, panicking if
   348  // it is not found or is zero. Zero values should have been
   349  // diagnosed at Validate time.
   350  func (c Config) mustInt(name string) int {
   351  	// Values obtained over the api are encoded as float64.
   352  	if value, ok := c[name].(float64); ok {
   353  		return int(value)
   354  	}
   355  	value, _ := c[name].(int)
   356  	if value == 0 {
   357  		panic(errors.Errorf("empty value for %q found in configuration", name))
   358  	}
   359  	return value
   360  }
   361  
   362  func (c Config) intOrDefault(name string, defaultVal int) int {
   363  	if _, ok := c[name]; ok {
   364  		return c.mustInt(name)
   365  	}
   366  	return defaultVal
   367  }
   368  
   369  // asString is a private helper method to keep the ugly string casting
   370  // in once place. It returns the given named attribute as a string,
   371  // returning "" if it isn't found.
   372  func (c Config) asString(name string) string {
   373  	value, _ := c[name].(string)
   374  	return value
   375  }
   376  
   377  // mustString returns the named attribute as an string, panicking if
   378  // it is not found or is empty.
   379  func (c Config) mustString(name string) string {
   380  	value, _ := c[name].(string)
   381  	if value == "" {
   382  		panic(errors.Errorf("empty value for %q found in configuration (type %T, val %v)", name, c[name], c[name]))
   383  	}
   384  	return value
   385  }
   386  
   387  // StatePort returns the controller port for the environment.
   388  func (c Config) StatePort() int {
   389  	return c.mustInt(StatePort)
   390  }
   391  
   392  // APIPort returns the API server port for the environment.
   393  func (c Config) APIPort() int {
   394  	return c.mustInt(APIPort)
   395  }
   396  
   397  // APIPortOpenDelay returns the duration to wait before opening
   398  // the APIPort once the controller has started up. Only used when
   399  // the ControllerAPIPort is non-zero.
   400  func (c Config) APIPortOpenDelay() time.Duration {
   401  	v := c.asString(APIPortOpenDelay)
   402  	// We know that v must be a parseable time.Duration for the config
   403  	// to be valid.
   404  	d, _ := time.ParseDuration(v)
   405  	return d
   406  }
   407  
   408  // ControllerAPIPort returns the optional API port to be used for
   409  // the controllers to talk to each other. A zero value means that
   410  // it is not set.
   411  func (c Config) ControllerAPIPort() int {
   412  	if value, ok := c[ControllerAPIPort].(float64); ok {
   413  		return int(value)
   414  	}
   415  	// If the value isn't an int, this conversion will fail and value
   416  	// will be 0, which is what we want here.
   417  	value, _ := c[ControllerAPIPort].(int)
   418  	return value
   419  }
   420  
   421  // AuditingEnabled returns whether or not auditing has been enabled
   422  // for the environment. The default is false.
   423  func (c Config) AuditingEnabled() bool {
   424  	if v, ok := c[AuditingEnabled]; ok {
   425  		return v.(bool)
   426  	}
   427  	return DefaultAuditingEnabled
   428  }
   429  
   430  // AuditLogCaptureArgs returns whether audit logging should capture
   431  // the arguments to API methods. The default is false.
   432  func (c Config) AuditLogCaptureArgs() bool {
   433  	if v, ok := c[AuditLogCaptureArgs]; ok {
   434  		return v.(bool)
   435  	}
   436  	return DefaultAuditLogCaptureArgs
   437  }
   438  
   439  // AuditLogMaxSizeMB returns the maximum size for an audit log file in
   440  // MB.
   441  func (c Config) AuditLogMaxSizeMB() int {
   442  	// Value has already been validated.
   443  	value, _ := utils.ParseSize(c.asString(AuditLogMaxSize))
   444  	return int(value)
   445  }
   446  
   447  // AuditLogMaxBackups returns the maximum number of backup audit log
   448  // files to keep.
   449  func (c Config) AuditLogMaxBackups() int {
   450  	return c.intOrDefault(AuditLogMaxBackups, DefaultAuditLogMaxBackups)
   451  }
   452  
   453  // AuditLogExcludeMethods returns the set of method names that are
   454  // considered uninteresting for audit logging. Conversations
   455  // containing only these will be excluded from the audit log.
   456  func (c Config) AuditLogExcludeMethods() set.Strings {
   457  	if value, ok := c[AuditLogExcludeMethods]; ok {
   458  		value := value.([]interface{})
   459  		items := set.NewStrings()
   460  		for _, item := range value {
   461  			items.Add(item.(string))
   462  		}
   463  		return items
   464  	}
   465  	return set.NewStrings(DefaultAuditLogExcludeMethods...)
   466  }
   467  
   468  // Features returns the controller config set features flags.
   469  func (c Config) Features() set.Strings {
   470  	features := set.NewStrings()
   471  	if value, ok := c[Features]; ok {
   472  		value := value.([]interface{})
   473  		for _, item := range value {
   474  			features.Add(item.(string))
   475  		}
   476  	}
   477  	return features
   478  }
   479  
   480  // CharmStoreURL returns the URL to use for charmstore api calls.
   481  func (c Config) CharmStoreURL() string {
   482  	url := c.asString(CharmStoreURL)
   483  	if url == "" {
   484  		return csclient.ServerURL
   485  	}
   486  	return url
   487  }
   488  
   489  // ControllerUUID returns the uuid for the model's controller.
   490  func (c Config) ControllerUUID() string {
   491  	return c.mustString(ControllerUUIDKey)
   492  }
   493  
   494  // CACert returns the certificate of the CA that signed the controller
   495  // certificate, in PEM format, and whether the setting is available.
   496  //
   497  // TODO(axw) once the controller config is completely constructed,
   498  // there will always be a CA certificate. Get rid of the bool result.
   499  func (c Config) CACert() (string, bool) {
   500  	if s, ok := c[CACertKey]; ok {
   501  		return s.(string), true
   502  	}
   503  	return "", false
   504  }
   505  
   506  // IdentityURL returns the url of the identity manager.
   507  func (c Config) IdentityURL() string {
   508  	return c.asString(IdentityURL)
   509  }
   510  
   511  // AutocertURL returns the URL used to obtain official TLS certificates
   512  // when a client connects to the API. See AutocertURLKey
   513  // for more details.
   514  func (c Config) AutocertURL() string {
   515  	return c.asString(AutocertURLKey)
   516  }
   517  
   518  // AutocertDNSName returns the DNS name of the controller.
   519  // See AutocertDNSNameKey for more details.
   520  func (c Config) AutocertDNSName() string {
   521  	return c.asString(AutocertDNSNameKey)
   522  }
   523  
   524  // IdentityPublicKey returns the public key of the identity manager.
   525  func (c Config) IdentityPublicKey() *bakery.PublicKey {
   526  	key := c.asString(IdentityPublicKey)
   527  	if key == "" {
   528  		return nil
   529  	}
   530  	var pubKey bakery.PublicKey
   531  	err := pubKey.UnmarshalText([]byte(key))
   532  	if err != nil {
   533  		// We check if the key string can be unmarshalled into a PublicKey in the
   534  		// Validate function, so we really do not expect this to fail.
   535  		panic(err)
   536  	}
   537  	return &pubKey
   538  }
   539  
   540  // MongoMemoryProfile returns the selected profile or low.
   541  func (c Config) MongoMemoryProfile() string {
   542  	if profile, ok := c[MongoMemoryProfile]; ok {
   543  		return profile.(string)
   544  	}
   545  	return MongoProfLow
   546  }
   547  
   548  // NUMACtlPreference returns if numactl is preferred.
   549  func (c Config) NUMACtlPreference() bool {
   550  	if numa, ok := c[SetNUMAControlPolicyKey]; ok {
   551  		return numa.(bool)
   552  	}
   553  	return DefaultNUMAControlPolicy
   554  }
   555  
   556  // AllowModelAccess reports whether users are allowed to access models
   557  // they have been granted permission for even when they can't access
   558  // the controller.
   559  func (c Config) AllowModelAccess() bool {
   560  	value, _ := c[AllowModelAccessKey].(bool)
   561  	return value
   562  }
   563  
   564  // MaxLogsAge is the maximum age of log entries before they are pruned.
   565  func (c Config) MaxLogsAge() time.Duration {
   566  	// Value has already been validated.
   567  	val, _ := time.ParseDuration(c.mustString(MaxLogsAge))
   568  	return val
   569  }
   570  
   571  // MaxLogSizeMB is the maximum size in MiB which the log collection
   572  // can grow to before being pruned.
   573  func (c Config) MaxLogSizeMB() int {
   574  	// Value has already been validated.
   575  	val, _ := utils.ParseSize(c.mustString(MaxLogsSize))
   576  	return int(val)
   577  }
   578  
   579  // MaxTxnLogSizeMB is the maximum size in MiB of the txn log collection.
   580  func (c Config) MaxTxnLogSizeMB() int {
   581  	// Value has already been validated.
   582  	val, _ := utils.ParseSize(c.mustString(MaxTxnLogSize))
   583  	return int(val)
   584  }
   585  
   586  // MaxPruneTxnBatchSize is the maximum size of the txn log collection.
   587  func (c Config) MaxPruneTxnBatchSize() int {
   588  	return c.intOrDefault(MaxPruneTxnBatchSize, DefaultMaxPruneTxnBatchSize)
   589  }
   590  
   591  // MaxPruneTxnPasses is the maximum number of batches of the txn log collection we will process at a time.
   592  func (c Config) MaxPruneTxnPasses() int {
   593  	return c.intOrDefault(MaxPruneTxnPasses, DefaultMaxPruneTxnPasses)
   594  }
   595  
   596  // PruneTxnQueryCount is the size of small batches for pruning
   597  func (c Config) PruneTxnQueryCount() int {
   598  	return c.intOrDefault(PruneTxnQueryCount, DefaultPruneTxnQueryCount)
   599  }
   600  
   601  // PruneTxnSleepTime is the amount of time to sleep between batches.
   602  func (c Config) PruneTxnSleepTime() time.Duration {
   603  	asInterface, ok := c[PruneTxnSleepTime]
   604  	if !ok {
   605  		asInterface = DefaultPruneTxnSleepTime
   606  	}
   607  	asStr, ok := asInterface.(string)
   608  	if !ok {
   609  		asStr = DefaultPruneTxnSleepTime
   610  	}
   611  	val, _ := time.ParseDuration(asStr)
   612  	return val
   613  }
   614  
   615  // JujuHASpace is the network space within which the MongoDB replica-set
   616  // should communicate.
   617  func (c Config) JujuHASpace() string {
   618  	return c.asString(JujuHASpace)
   619  }
   620  
   621  // JujuManagementSpace is the network space that agents should use to
   622  // communicate with controllers.
   623  func (c Config) JujuManagementSpace() string {
   624  	return c.asString(JujuManagementSpace)
   625  }
   626  
   627  // CAASOperatorImagePath sets the url of the docker image
   628  // used for the application operator.
   629  func (c Config) CAASOperatorImagePath() string {
   630  	return c.asString(CAASOperatorImagePath)
   631  }
   632  
   633  // MeteringURL returns the URL to use for metering api calls.
   634  func (c Config) MeteringURL() string {
   635  	url := c.asString(MeteringURL)
   636  	if url == "" {
   637  		return romulus.DefaultAPIRoot
   638  	}
   639  	return url
   640  }
   641  
   642  // Validate ensures that config is a valid configuration.
   643  func Validate(c Config) error {
   644  	if v, ok := c[IdentityPublicKey].(string); ok {
   645  		var key bakery.PublicKey
   646  		if err := key.UnmarshalText([]byte(v)); err != nil {
   647  			return errors.Annotate(err, "invalid identity public key")
   648  		}
   649  	}
   650  
   651  	if v, ok := c[IdentityURL].(string); ok {
   652  		u, err := url.Parse(v)
   653  		if err != nil {
   654  			return errors.Annotate(err, "invalid identity URL")
   655  		}
   656  		// If we've got an identity public key, we allow an HTTP
   657  		// scheme for the identity server because we won't need
   658  		// to rely on insecure transport to obtain the public
   659  		// key.
   660  		if _, ok := c[IdentityPublicKey]; !ok && u.Scheme != "https" {
   661  			return errors.Errorf("URL needs to be https when %s not provided", IdentityPublicKey)
   662  		}
   663  	}
   664  
   665  	caCert, caCertOK := c.CACert()
   666  	if !caCertOK {
   667  		return errors.Errorf("missing CA certificate")
   668  	}
   669  	if _, err := utilscert.ParseCert(caCert); err != nil {
   670  		return errors.Annotate(err, "bad CA certificate in configuration")
   671  	}
   672  
   673  	if uuid, ok := c[ControllerUUIDKey].(string); ok && !utils.IsValidUUIDString(uuid) {
   674  		return errors.Errorf("controller-uuid: expected UUID, got string(%q)", uuid)
   675  	}
   676  
   677  	if mgoMemProfile, ok := c[MongoMemoryProfile].(string); ok {
   678  		if mgoMemProfile != MongoProfLow && mgoMemProfile != MongoProfDefault {
   679  			return errors.Errorf("mongo-memory-profile: expected one of %s or %s got string(%q)", MongoProfLow, MongoProfDefault, mgoMemProfile)
   680  		}
   681  	}
   682  
   683  	if v, ok := c[MaxLogsAge].(string); ok {
   684  		if _, err := time.ParseDuration(v); err != nil {
   685  			return errors.Annotate(err, "invalid logs prune interval in configuration")
   686  		}
   687  	}
   688  
   689  	if v, ok := c[MaxLogsSize].(string); ok {
   690  		if _, err := utils.ParseSize(v); err != nil {
   691  			return errors.Annotate(err, "invalid max logs size in configuration")
   692  		}
   693  	}
   694  
   695  	if v, ok := c[MaxTxnLogSize].(string); ok {
   696  		if _, err := utils.ParseSize(v); err != nil {
   697  			return errors.Annotate(err, "invalid max txn log size in configuration")
   698  		}
   699  	}
   700  
   701  	if v, ok := c[PruneTxnSleepTime].(string); ok {
   702  		if _, err := time.ParseDuration(v); err != nil {
   703  			return errors.Annotatef(err, `%s must be a valid duration (eg "10ms")`, PruneTxnSleepTime)
   704  		}
   705  	}
   706  
   707  	if err := c.validateSpaceConfig(JujuHASpace, "juju HA"); err != nil {
   708  		return errors.Trace(err)
   709  	}
   710  
   711  	if err := c.validateSpaceConfig(JujuManagementSpace, "juju mgmt"); err != nil {
   712  		return errors.Trace(err)
   713  	}
   714  
   715  	if v, ok := c[CAASOperatorImagePath].(string); ok {
   716  		if err := resources.ValidateDockerRegistryPath(v); err != nil {
   717  			return errors.Trace(err)
   718  		}
   719  	}
   720  
   721  	var auditLogMaxSize int
   722  	if v, ok := c[AuditLogMaxSize].(string); ok {
   723  		if size, err := utils.ParseSize(v); err != nil {
   724  			return errors.Annotate(err, "invalid audit log max size in configuration")
   725  		} else {
   726  			auditLogMaxSize = int(size)
   727  		}
   728  	}
   729  
   730  	if v, ok := c[AuditingEnabled].(bool); ok {
   731  		if v && auditLogMaxSize == 0 {
   732  			return errors.Errorf("invalid audit log max size: can't be 0 if auditing is enabled")
   733  		}
   734  	}
   735  
   736  	if v, ok := c[AuditLogMaxBackups].(int); ok {
   737  		if v < 0 {
   738  			return errors.Errorf("invalid audit log max backups: should be a number of files (or 0 to keep all), got %d", v)
   739  		}
   740  	}
   741  
   742  	if v, ok := c[AuditLogExcludeMethods].([]interface{}); ok {
   743  		for i, name := range v {
   744  			name := name.(string)
   745  			if name != ReadOnlyMethodsWildcard && !methodNameRE.MatchString(name) {
   746  				return errors.Errorf(
   747  					`invalid audit log exclude methods: should be a list of "Facade.Method" names (or "ReadOnlyMethods"), got %q at position %d`,
   748  					name,
   749  					i+1,
   750  				)
   751  			}
   752  		}
   753  	}
   754  
   755  	if v, ok := c[ControllerAPIPort].(int); ok {
   756  		// TODO: change the validation so 0 is invalide and --reset is used.
   757  		// However that doesn't exist yet.
   758  		if v < 0 {
   759  			return errors.NotValidf("non-positive integer for controller-api-port")
   760  		}
   761  		if v == c.APIPort() {
   762  			return errors.NotValidf("controller-api-port matching api-port")
   763  		}
   764  		if v == c.StatePort() {
   765  			return errors.NotValidf("controller-api-port matching state-port")
   766  		}
   767  	}
   768  	if v, ok := c[APIPortOpenDelay].(string); ok {
   769  		_, err := time.ParseDuration(v)
   770  		if err != nil {
   771  			return errors.Errorf("%s value %q must be a valid duration", APIPortOpenDelay, v)
   772  		}
   773  	}
   774  
   775  	return nil
   776  }
   777  
   778  func (c Config) validateSpaceConfig(key, topic string) error {
   779  	val := c[key]
   780  	if val == nil {
   781  		return nil
   782  	}
   783  	if v, ok := val.(string); ok {
   784  		if !names.IsValidSpace(v) {
   785  			return errors.NotValidf("%s space name %q", topic, val)
   786  		}
   787  	} else {
   788  		return errors.NotValidf("type for %s space name %v", topic, val)
   789  	}
   790  
   791  	return nil
   792  }
   793  
   794  // AsSpaceConstraints checks to see whether config has spaces names populated
   795  // for management and/or HA (Mongo).
   796  // Non-empty values are merged with any input spaces and returned as a new
   797  // slice reference.
   798  // A slice pointer is used for congruence with the Spaces member in
   799  // constraints.Value.
   800  func (c Config) AsSpaceConstraints(spaces *[]string) *[]string {
   801  	newSpaces := set.NewStrings()
   802  	if spaces != nil {
   803  		for _, s := range *spaces {
   804  			newSpaces.Add(s)
   805  		}
   806  	}
   807  
   808  	for _, c := range []string{c.JujuManagementSpace(), c.JujuHASpace()} {
   809  		if c != "" {
   810  			newSpaces.Add(c)
   811  		}
   812  	}
   813  
   814  	// Preserve a nil pointer if there is no change. This conveys information
   815  	// in constraints.Value (not set vs. deliberately set as empty).
   816  	if spaces == nil && len(newSpaces) == 0 {
   817  		return nil
   818  	}
   819  	ns := newSpaces.SortedValues()
   820  	return &ns
   821  }
   822  
   823  // GenerateControllerCertAndKey makes sure that the config has a CACert and
   824  // CAPrivateKey, generates and returns new certificate and key.
   825  func GenerateControllerCertAndKey(caCert, caKey string, hostAddresses []string) (string, string, error) {
   826  	return cert.NewDefaultServer(caCert, caKey, hostAddresses)
   827  }
   828  
   829  var configChecker = schema.FieldMap(schema.Fields{
   830  	AuditingEnabled:         schema.Bool(),
   831  	AuditLogCaptureArgs:     schema.Bool(),
   832  	AuditLogMaxSize:         schema.String(),
   833  	AuditLogMaxBackups:      schema.ForceInt(),
   834  	AuditLogExcludeMethods:  schema.List(schema.String()),
   835  	APIPort:                 schema.ForceInt(),
   836  	APIPortOpenDelay:        schema.String(),
   837  	ControllerAPIPort:       schema.ForceInt(),
   838  	StatePort:               schema.ForceInt(),
   839  	IdentityURL:             schema.String(),
   840  	IdentityPublicKey:       schema.String(),
   841  	SetNUMAControlPolicyKey: schema.Bool(),
   842  	AutocertURLKey:          schema.String(),
   843  	AutocertDNSNameKey:      schema.String(),
   844  	AllowModelAccessKey:     schema.Bool(),
   845  	MongoMemoryProfile:      schema.String(),
   846  	MaxLogsAge:              schema.String(),
   847  	MaxLogsSize:             schema.String(),
   848  	MaxTxnLogSize:           schema.String(),
   849  	MaxPruneTxnBatchSize:    schema.ForceInt(),
   850  	MaxPruneTxnPasses:       schema.ForceInt(),
   851  	PruneTxnQueryCount:      schema.ForceInt(),
   852  	PruneTxnSleepTime:       schema.String(),
   853  	JujuHASpace:             schema.String(),
   854  	JujuManagementSpace:     schema.String(),
   855  	CAASOperatorImagePath:   schema.String(),
   856  	Features:                schema.List(schema.String()),
   857  	CharmStoreURL:           schema.String(),
   858  	MeteringURL:             schema.String(),
   859  }, schema.Defaults{
   860  	APIPort:                 DefaultAPIPort,
   861  	APIPortOpenDelay:        DefaultAPIPortOpenDelay,
   862  	ControllerAPIPort:       schema.Omit,
   863  	AuditingEnabled:         DefaultAuditingEnabled,
   864  	AuditLogCaptureArgs:     DefaultAuditLogCaptureArgs,
   865  	AuditLogMaxSize:         fmt.Sprintf("%vM", DefaultAuditLogMaxSizeMB),
   866  	AuditLogMaxBackups:      DefaultAuditLogMaxBackups,
   867  	AuditLogExcludeMethods:  DefaultAuditLogExcludeMethods,
   868  	StatePort:               DefaultStatePort,
   869  	IdentityURL:             schema.Omit,
   870  	IdentityPublicKey:       schema.Omit,
   871  	SetNUMAControlPolicyKey: DefaultNUMAControlPolicy,
   872  	AutocertURLKey:          schema.Omit,
   873  	AutocertDNSNameKey:      schema.Omit,
   874  	AllowModelAccessKey:     schema.Omit,
   875  	MongoMemoryProfile:      schema.Omit,
   876  	MaxLogsAge:              fmt.Sprintf("%vh", DefaultMaxLogsAgeDays*24),
   877  	MaxLogsSize:             fmt.Sprintf("%vM", DefaultMaxLogCollectionMB),
   878  	MaxTxnLogSize:           fmt.Sprintf("%vM", DefaultMaxTxnLogCollectionMB),
   879  	MaxPruneTxnBatchSize:    DefaultMaxPruneTxnBatchSize,
   880  	MaxPruneTxnPasses:       DefaultMaxPruneTxnPasses,
   881  	PruneTxnQueryCount:      DefaultPruneTxnQueryCount,
   882  	PruneTxnSleepTime:       DefaultPruneTxnSleepTime,
   883  	JujuHASpace:             schema.Omit,
   884  	JujuManagementSpace:     schema.Omit,
   885  	CAASOperatorImagePath:   schema.Omit,
   886  	Features:                schema.Omit,
   887  	CharmStoreURL:           csclient.ServerURL,
   888  	MeteringURL:             romulus.DefaultAPIRoot,
   889  })