github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/controller/config_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller_test
     5  
     6  import (
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"fmt"
    10  	stdtesting "testing"
    11  	"time"
    12  
    13  	"github.com/juju/collections/set"
    14  	"github.com/juju/loggo"
    15  	"github.com/juju/romulus"
    16  	jc "github.com/juju/testing/checkers"
    17  	"go.uber.org/mock/gomock"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/controller"
    21  	"github.com/juju/juju/docker"
    22  	"github.com/juju/juju/docker/registry"
    23  	"github.com/juju/juju/docker/registry/mocks"
    24  	"github.com/juju/juju/testing"
    25  )
    26  
    27  func Test(t *stdtesting.T) {
    28  	gc.TestingT(t)
    29  }
    30  
    31  type ConfigSuite struct {
    32  	testing.FakeJujuXDGDataHomeSuite
    33  }
    34  
    35  var _ = gc.Suite(&ConfigSuite{})
    36  
    37  func (s *ConfigSuite) SetUpTest(c *gc.C) {
    38  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
    39  	// Make sure that the defaults are used, which
    40  	// is <root>=WARNING
    41  	loggo.DefaultContext().ResetLoggerLevels()
    42  }
    43  
    44  var validateTests = []struct {
    45  	about       string
    46  	config      controller.Config
    47  	expectError string
    48  }{{
    49  	about:       "missing CA cert",
    50  	expectError: `missing CA certificate`,
    51  }, {
    52  	about: "bad CA cert",
    53  	config: controller.Config{
    54  		controller.CACertKey: "xxx",
    55  	},
    56  	expectError: `bad CA certificate in configuration: no certificates in pem bundle`,
    57  }, {
    58  	about: "bad controller UUID",
    59  	config: controller.Config{
    60  		controller.ControllerUUIDKey: "xxx",
    61  		controller.CACertKey:         testing.CACert,
    62  	},
    63  	expectError: `controller-uuid: expected UUID, got string\("xxx"\)`,
    64  }}
    65  
    66  func (s *ConfigSuite) TestValidate(c *gc.C) {
    67  	// Normally Validate is only called as part of the NewConfig call, which
    68  	// also does schema coercing. The NewConfig method takes the controller uuid
    69  	// and cacert as separate args, so to get invalid ones, we skip that part.
    70  	for i, test := range validateTests {
    71  		c.Logf("test %d: %v", i, test.about)
    72  		err := test.config.Validate()
    73  		if test.expectError != "" {
    74  			c.Check(err, gc.ErrorMatches, test.expectError)
    75  		} else {
    76  			c.Check(err, jc.ErrorIsNil)
    77  		}
    78  	}
    79  }
    80  
    81  var newConfigTests = []struct {
    82  	about       string
    83  	config      controller.Config
    84  	expectError string
    85  }{{
    86  	about: "HTTPS identity URL OK",
    87  	config: controller.Config{
    88  		controller.IdentityURL: "https://0.1.2.3/foo",
    89  	},
    90  }, {
    91  	about: "HTTP identity URL requires public key",
    92  	config: controller.Config{
    93  		controller.IdentityURL: "http://0.1.2.3/foo",
    94  	},
    95  	expectError: `URL needs to be https when identity-public-key not provided`,
    96  }, {
    97  	about: "HTTP identity URL OK if public key is provided",
    98  	config: controller.Config{
    99  		controller.IdentityPublicKey: `o/yOqSNWncMo1GURWuez/dGR30TscmmuIxgjztpoHEY=`,
   100  		controller.IdentityURL:       "http://0.1.2.3/foo",
   101  	},
   102  }, {
   103  	about: "feature flags mys be a list",
   104  	config: controller.Config{
   105  		controller.Features: "foo",
   106  	},
   107  	expectError: `features: expected list, got string\("foo"\)`,
   108  }, {
   109  	about: "invalid identity public key",
   110  	config: controller.Config{
   111  		controller.IdentityPublicKey: `xxxx`,
   112  	},
   113  	expectError: `invalid identity public key: wrong length for key, got 3 want 32`,
   114  }, {
   115  	about: "invalid management space name - whitespace",
   116  	config: controller.Config{
   117  		controller.JujuManagementSpace: " ",
   118  	},
   119  	expectError: `juju mgmt space name " " not valid`,
   120  }, {
   121  	about: "invalid management space name - caps",
   122  	config: controller.Config{
   123  		controller.JujuManagementSpace: "CAPS",
   124  	},
   125  	expectError: `juju mgmt space name "CAPS" not valid`,
   126  }, {
   127  	about: "invalid management space name - carriage return",
   128  	config: controller.Config{
   129  		controller.JujuManagementSpace: "\n",
   130  	},
   131  	expectError: `juju mgmt space name "\\n" not valid`,
   132  }, {
   133  	about: "invalid HA space name - number",
   134  	config: controller.Config{
   135  		controller.JujuHASpace: 666,
   136  	},
   137  	expectError: `juju-ha-space: expected string, got int\(666\)`,
   138  }, {
   139  	about: "invalid HA space name - bool",
   140  	config: controller.Config{
   141  		controller.JujuHASpace: true,
   142  	},
   143  	expectError: `juju-ha-space: expected string, got bool\(true\)`,
   144  }, {
   145  	about: "invalid audit log max size",
   146  	config: controller.Config{
   147  		controller.AuditLogMaxSize: "abcd",
   148  	},
   149  	expectError: `invalid audit log max size in configuration: expected a non-negative number, got "abcd"`,
   150  }, {
   151  	about: "zero audit log max size",
   152  	config: controller.Config{
   153  		controller.AuditingEnabled: true,
   154  		controller.AuditLogMaxSize: "0M",
   155  	},
   156  	expectError: `invalid audit log max size: can't be 0 if auditing is enabled`,
   157  }, {
   158  	about: "invalid audit log max backups",
   159  	config: controller.Config{
   160  		controller.AuditLogMaxBackups: -10,
   161  	},
   162  	expectError: `invalid audit log max backups: should be a number of files \(or 0 to keep all\), got -10`,
   163  }, {
   164  	about: "invalid audit log exclude",
   165  	config: controller.Config{
   166  		controller.AuditLogExcludeMethods: []interface{}{"Dap.Kings", "ReadOnlyMethods", "Sharon Jones"},
   167  	},
   168  	expectError: `invalid audit log exclude methods: should be a list of "Facade.Method" names \(or "ReadOnlyMethods"\), got "Sharon Jones" at position 3`,
   169  }, {
   170  	about: "invalid model log max size",
   171  	config: controller.Config{
   172  		controller.ModelLogsSize: "abcd",
   173  	},
   174  	expectError: `invalid model logs size in configuration: expected a non-negative number, got "abcd"`,
   175  }, {
   176  	about: "zero model log max size",
   177  	config: controller.Config{
   178  		controller.ModelLogsSize: "0",
   179  	},
   180  	expectError: "model logs size less than 1 MB not valid",
   181  }, {
   182  	about: "negative controller-api-port",
   183  	config: controller.Config{
   184  		controller.ControllerAPIPort: -5,
   185  	},
   186  	expectError: `non-positive integer for controller-api-port not valid`,
   187  }, {
   188  	about: "controller-api-port matching api-port",
   189  	config: controller.Config{
   190  		controller.APIPort:           12345,
   191  		controller.ControllerAPIPort: 12345,
   192  	},
   193  	expectError: `controller-api-port matching api-port not valid`,
   194  }, {
   195  	about: "controller-api-port matching state-port",
   196  	config: controller.Config{
   197  		controller.APIPort:           12345,
   198  		controller.StatePort:         54321,
   199  		controller.ControllerAPIPort: 54321,
   200  	},
   201  	expectError: `controller-api-port matching state-port not valid`,
   202  }, {
   203  	about: "api-port-open-delay not a duration",
   204  	config: controller.Config{
   205  		controller.APIPortOpenDelay: "15",
   206  	},
   207  	expectError: `api-port-open-delay: conversion to duration: time: missing unit in duration "15"`,
   208  }, {
   209  	about: "txn-prune-sleep-time not a duration",
   210  	config: controller.Config{
   211  		controller.PruneTxnSleepTime: "15",
   212  	},
   213  	expectError: `prune-txn-sleep-time: conversion to duration: time: missing unit in duration "15"`,
   214  }, {
   215  	about: "mongo-memory-profile not valid",
   216  	config: controller.Config{
   217  		controller.MongoMemoryProfile: "not-valid",
   218  	},
   219  	expectError: `mongo-memory-profile: expected one of "low" or "default" got string\("not-valid"\)`,
   220  }, {
   221  	about: "max-debug-log-duration not valid",
   222  	config: controller.Config{
   223  		controller.MaxDebugLogDuration: time.Duration(0),
   224  	},
   225  	expectError: `max-debug-log-duration cannot be zero`,
   226  }, {
   227  	about: "agent-logfile-max-backups not valid",
   228  	config: controller.Config{
   229  		controller.AgentLogfileMaxBackups: -1,
   230  	},
   231  	expectError: `negative agent-logfile-max-backups not valid`,
   232  }, {
   233  	about: "agent-logfile-max-size not valid",
   234  	config: controller.Config{
   235  		controller.AgentLogfileMaxSize: "0",
   236  	},
   237  	expectError: `agent-logfile-max-size less than 1 MB not valid`,
   238  }, {
   239  	about: "model-logfile-max-backups not valid",
   240  	config: controller.Config{
   241  		controller.ModelLogfileMaxBackups: -1,
   242  	},
   243  	expectError: `negative model-logfile-max-backups not valid`,
   244  }, {
   245  	about: "model-logfile-max-size not valid",
   246  	config: controller.Config{
   247  		controller.ModelLogfileMaxSize: "0",
   248  	},
   249  	expectError: `model-logfile-max-size less than 1 MB not valid`,
   250  }, {
   251  	about: "agent-ratelimit-max non-int",
   252  	config: controller.Config{
   253  		controller.AgentRateLimitMax: "ten",
   254  	},
   255  	expectError: `agent-ratelimit-max: expected number, got string\("ten"\)`,
   256  }, {
   257  	about: "agent-ratelimit-max negative",
   258  	config: controller.Config{
   259  		controller.AgentRateLimitMax: "-5",
   260  	},
   261  	expectError: `negative agent-ratelimit-max \(-5\) not valid`,
   262  }, {
   263  	about: "agent-ratelimit-rate missing unit",
   264  	config: controller.Config{
   265  		controller.AgentRateLimitRate: "150",
   266  	},
   267  	expectError: `agent-ratelimit-rate: conversion to duration: time: missing unit in duration "?150"?`,
   268  }, {
   269  	about: "agent-ratelimit-rate bad type, int",
   270  	config: controller.Config{
   271  		controller.AgentRateLimitRate: 150,
   272  	},
   273  	expectError: `agent-ratelimit-rate: expected string or time.Duration, got int\(150\)`,
   274  }, {
   275  	about: "agent-ratelimit-rate zero",
   276  	config: controller.Config{
   277  		controller.AgentRateLimitRate: "0s",
   278  	},
   279  	expectError: `agent-ratelimit-rate cannot be zero`,
   280  }, {
   281  	about: "agent-ratelimit-rate negative",
   282  	config: controller.Config{
   283  		controller.AgentRateLimitRate: "-5s",
   284  	},
   285  	expectError: `agent-ratelimit-rate cannot be negative`,
   286  }, {
   287  	about: "agent-ratelimit-rate too large",
   288  	config: controller.Config{
   289  		controller.AgentRateLimitRate: "4h",
   290  	},
   291  	expectError: `agent-ratelimit-rate must be between 0..1m`,
   292  }, {
   293  	about: "max-charm-state-size non-int",
   294  	config: controller.Config{
   295  		controller.MaxCharmStateSize: "ten",
   296  	},
   297  	expectError: `max-charm-state-size: expected number, got string\("ten"\)`,
   298  }, {
   299  	about: "max-charm-state-size cannot be negative",
   300  	config: controller.Config{
   301  		controller.MaxCharmStateSize: "-42",
   302  	},
   303  	expectError: `invalid max charm state size: should be a number of bytes \(or 0 to disable limit\), got -42`,
   304  }, {
   305  	about: "max-agent-state-size non-int",
   306  	config: controller.Config{
   307  		controller.MaxAgentStateSize: "ten",
   308  	},
   309  	expectError: `max-agent-state-size: expected number, got string\("ten"\)`,
   310  }, {
   311  	about: "max-agent-state-size cannot be negative",
   312  	config: controller.Config{
   313  		controller.MaxAgentStateSize: "-42",
   314  	},
   315  	expectError: `invalid max agent state size: should be a number of bytes \(or 0 to disable limit\), got -42`,
   316  }, {
   317  	about: "combined charm/agent state cannot exceed mongo's 16M limit/doc",
   318  	config: controller.Config{
   319  		controller.MaxCharmStateSize: "14000000",
   320  		controller.MaxAgentStateSize: "3000000",
   321  	},
   322  	expectError: `invalid max charm/agent state sizes: combined value should not exceed mongo's 16M per-document limit, got 17000000`,
   323  }, {
   324  	about: "public-dns-address: expect string, got number",
   325  	config: controller.Config{
   326  		controller.PublicDNSAddress: 42,
   327  	},
   328  	expectError: `public-dns-address: expected string, got int\(42\)`,
   329  }, {
   330  	about: "migration-agent-wait-time not a duration",
   331  	config: controller.Config{
   332  		controller.MigrationMinionWaitMax: "15",
   333  	},
   334  	expectError: `migration-agent-wait-time: conversion to duration: time: missing unit in duration "15"`,
   335  }, {
   336  	about: "application-resource-download-limit cannot be negative",
   337  	config: controller.Config{
   338  		controller.ApplicationResourceDownloadLimit: "-42",
   339  	},
   340  	expectError: `negative application-resource-download-limit \(-42\) not valid, use 0 to disable the limit`,
   341  }, {
   342  	about: "controller-resource-download-limit cannot be negative",
   343  	config: controller.Config{
   344  		controller.ControllerResourceDownloadLimit: "-42",
   345  	},
   346  	expectError: `negative controller-resource-download-limit \(-42\) not valid, use 0 to disable the limit`,
   347  }, {
   348  	about: "login token refresh url",
   349  	config: controller.Config{
   350  		controller.LoginTokenRefreshURL: `https://xxxx`,
   351  	},
   352  }, {
   353  	about: "invalid login token refresh url",
   354  	config: controller.Config{
   355  		controller.LoginTokenRefreshURL: `xxxx`,
   356  	},
   357  	expectError: `logic token refresh URL "xxxx" not valid`,
   358  }, {
   359  	about: "invalid query tracing value",
   360  	config: controller.Config{
   361  		controller.QueryTracingEnabled: "invalid",
   362  	},
   363  	expectError: `query-tracing-enabled: expected bool, got string\("invalid"\)`,
   364  }, {
   365  	about: "invalid query tracing threshold value",
   366  	config: controller.Config{
   367  		controller.QueryTracingThreshold: "invalid",
   368  	},
   369  	expectError: `query-tracing-threshold: conversion to duration: time: invalid duration "invalid"`,
   370  }, {
   371  	about: "negative query tracing threshold duration",
   372  	config: controller.Config{
   373  		controller.QueryTracingThreshold: "-1s",
   374  	},
   375  	expectError: `query-tracing-threshold value "-1s" must be a positive duration`,
   376  }, {
   377  	about: "invalid jujud-controller-snap-source value",
   378  	config: controller.Config{
   379  		controller.JujudControllerSnapSource: "latest/stable",
   380  	},
   381  	expectError: `jujud-controller-snap-source value "latest/stable" must be one of legacy, snapstore, local or local-dangerous.`,
   382  }}
   383  
   384  func (s *ConfigSuite) TestNewConfig(c *gc.C) {
   385  	for i, test := range newConfigTests {
   386  		c.Logf("test %d: %v", i, test.about)
   387  		_, err := controller.NewConfig(testing.ControllerTag.Id(), testing.CACert, test.config)
   388  		if test.expectError != "" {
   389  			c.Check(err, gc.ErrorMatches, test.expectError)
   390  		} else {
   391  			c.Check(err, jc.ErrorIsNil)
   392  		}
   393  	}
   394  }
   395  
   396  func (s *ConfigSuite) TestAPIPortDefaults(c *gc.C) {
   397  	cfg, err := controller.NewConfig(testing.ControllerTag.Id(), testing.CACert, nil)
   398  	c.Assert(err, jc.ErrorIsNil)
   399  	c.Assert(cfg.APIPortOpenDelay(), gc.Equals, 2*time.Second)
   400  }
   401  
   402  func (s *ConfigSuite) TestLogConfigDefaults(c *gc.C) {
   403  	cfg, err := controller.NewConfig(testing.ControllerTag.Id(), testing.CACert, nil)
   404  	c.Assert(err, jc.ErrorIsNil)
   405  	c.Assert(cfg.ModelLogsSizeMB(), gc.Equals, 20)
   406  }
   407  
   408  func (s *ConfigSuite) TestResourceDownloadLimits(c *gc.C) {
   409  	cfg, err := controller.NewConfig(
   410  		testing.ControllerTag.Id(),
   411  		testing.CACert,
   412  		map[string]interface{}{
   413  			"application-resource-download-limit": "42",
   414  			"controller-resource-download-limit":  "666",
   415  		},
   416  	)
   417  	c.Assert(err, jc.ErrorIsNil)
   418  	c.Assert(cfg.ApplicationResourceDownloadLimit(), gc.Equals, 42)
   419  	c.Assert(cfg.ControllerResourceDownloadLimit(), gc.Equals, 666)
   420  }
   421  
   422  func (s *ConfigSuite) TestLogConfigValues(c *gc.C) {
   423  	c.Assert(controller.AllowedUpdateConfigAttributes.Contains(controller.ModelLogsSize), jc.IsTrue)
   424  
   425  	cfg, err := controller.NewConfig(
   426  		testing.ControllerTag.Id(),
   427  		testing.CACert,
   428  		map[string]interface{}{
   429  			"max-logs-size":   "8G",
   430  			"max-logs-age":    "96h",
   431  			"model-logs-size": "35M",
   432  		},
   433  	)
   434  	c.Assert(err, jc.ErrorIsNil)
   435  	c.Assert(cfg.ModelLogsSizeMB(), gc.Equals, 35)
   436  }
   437  
   438  func (s *ConfigSuite) TestTxnLogConfigDefault(c *gc.C) {
   439  	cfg, err := controller.NewConfig(testing.ControllerTag.Id(), testing.CACert, nil)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	c.Assert(cfg.MaxTxnLogSizeMB(), gc.Equals, 10)
   442  }
   443  
   444  func (s *ConfigSuite) TestTxnLogConfigValue(c *gc.C) {
   445  	cfg, err := controller.NewConfig(
   446  		testing.ControllerTag.Id(),
   447  		testing.CACert,
   448  		map[string]interface{}{
   449  			"max-txn-log-size": "8G",
   450  		},
   451  	)
   452  	c.Assert(err, jc.ErrorIsNil)
   453  	c.Assert(cfg.MaxTxnLogSizeMB(), gc.Equals, 8192)
   454  }
   455  
   456  func (s *ConfigSuite) TestMaxPruneTxnConfigDefault(c *gc.C) {
   457  	cfg, err := controller.NewConfig(testing.ControllerTag.Id(), testing.CACert, nil)
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	c.Check(cfg.MaxPruneTxnBatchSize(), gc.Equals, 1*1000*1000)
   460  	c.Check(cfg.MaxPruneTxnPasses(), gc.Equals, 100)
   461  }
   462  
   463  func (s *ConfigSuite) TestMaxPruneTxnConfigValue(c *gc.C) {
   464  	cfg, err := controller.NewConfig(
   465  		testing.ControllerTag.Id(),
   466  		testing.CACert,
   467  		map[string]interface{}{
   468  			"max-prune-txn-batch-size": "12345678",
   469  			"max-prune-txn-passes":     "10",
   470  		},
   471  	)
   472  	c.Assert(err, jc.ErrorIsNil)
   473  	c.Check(cfg.MaxPruneTxnBatchSize(), gc.Equals, 12345678)
   474  	c.Check(cfg.MaxPruneTxnPasses(), gc.Equals, 10)
   475  }
   476  
   477  func (s *ConfigSuite) TestPruneTxnQueryCount(c *gc.C) {
   478  	cfg, err := controller.NewConfig(
   479  		testing.ControllerTag.Id(),
   480  		testing.CACert,
   481  		map[string]interface{}{
   482  			"prune-txn-query-count": "500",
   483  			"prune-txn-sleep-time":  "5ms",
   484  		},
   485  	)
   486  	c.Assert(err, jc.ErrorIsNil)
   487  	c.Check(cfg.PruneTxnQueryCount(), gc.Equals, 500)
   488  	c.Check(cfg.PruneTxnSleepTime(), gc.Equals, 5*time.Millisecond)
   489  }
   490  
   491  func (s *ConfigSuite) TestPublicDNSAddressConfigValue(c *gc.C) {
   492  	cfg, err := controller.NewConfig(
   493  		testing.ControllerTag.Id(),
   494  		testing.CACert,
   495  		map[string]interface{}{
   496  			"public-dns-address": "controller.test.com:12345",
   497  		},
   498  	)
   499  	c.Assert(err, jc.ErrorIsNil)
   500  	c.Check(cfg.PublicDNSAddress(), gc.Equals, "controller.test.com:12345")
   501  }
   502  
   503  func (s *ConfigSuite) TestNetworkSpaceConfigValues(c *gc.C) {
   504  	haSpace := "space1"
   505  	managementSpace := "space2"
   506  
   507  	cfg, err := controller.NewConfig(
   508  		testing.ControllerTag.Id(),
   509  		testing.CACert,
   510  		map[string]interface{}{
   511  			controller.JujuHASpace:         haSpace,
   512  			controller.JujuManagementSpace: managementSpace,
   513  		},
   514  	)
   515  	c.Assert(err, jc.ErrorIsNil)
   516  	c.Assert(cfg.JujuHASpace(), gc.Equals, haSpace)
   517  	c.Assert(cfg.JujuManagementSpace(), gc.Equals, managementSpace)
   518  }
   519  
   520  func (s *ConfigSuite) TestNetworkSpaceConfigDefaults(c *gc.C) {
   521  	cfg, err := controller.NewConfig(
   522  		testing.ControllerTag.Id(),
   523  		testing.CACert,
   524  		map[string]interface{}{},
   525  	)
   526  	c.Assert(err, jc.ErrorIsNil)
   527  	c.Assert(cfg.JujuHASpace(), gc.Equals, "")
   528  	c.Assert(cfg.JujuManagementSpace(), gc.Equals, "")
   529  }
   530  
   531  func (s *ConfigSuite) TestAuditLogDefaults(c *gc.C) {
   532  	cfg, err := controller.NewConfig(testing.ControllerTag.Id(), testing.CACert, nil)
   533  	c.Assert(err, jc.ErrorIsNil)
   534  	c.Assert(cfg.AuditingEnabled(), gc.Equals, true)
   535  	c.Assert(cfg.AuditLogCaptureArgs(), gc.Equals, false)
   536  	c.Assert(cfg.AuditLogMaxSizeMB(), gc.Equals, 300)
   537  	c.Assert(cfg.AuditLogMaxBackups(), gc.Equals, 10)
   538  	c.Assert(cfg.AuditLogExcludeMethods(), gc.DeepEquals,
   539  		set.NewStrings(controller.DefaultAuditLogExcludeMethods...))
   540  }
   541  
   542  func (s *ConfigSuite) TestAuditLogValues(c *gc.C) {
   543  	cfg, err := controller.NewConfig(
   544  		testing.ControllerTag.Id(),
   545  		testing.CACert,
   546  		map[string]interface{}{
   547  			"auditing-enabled":          false,
   548  			"audit-log-capture-args":    true,
   549  			"audit-log-max-size":        "100M",
   550  			"audit-log-max-backups":     10.0,
   551  			"audit-log-exclude-methods": []string{"Fleet.Foxes", "King.Gizzard", "ReadOnlyMethods"},
   552  		},
   553  	)
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	c.Assert(cfg.AuditingEnabled(), gc.Equals, false)
   556  	c.Assert(cfg.AuditLogCaptureArgs(), gc.Equals, true)
   557  	c.Assert(cfg.AuditLogMaxSizeMB(), gc.Equals, 100)
   558  	c.Assert(cfg.AuditLogMaxBackups(), gc.Equals, 10)
   559  	c.Assert(cfg.AuditLogExcludeMethods(), gc.DeepEquals, set.NewStrings(
   560  		"Fleet.Foxes",
   561  		"King.Gizzard",
   562  		"ReadOnlyMethods",
   563  	))
   564  }
   565  
   566  func (s *ConfigSuite) TestAuditLogExcludeMethodsType(c *gc.C) {
   567  	_, err := controller.NewConfig(
   568  		testing.ControllerTag.Id(),
   569  		testing.CACert,
   570  		map[string]interface{}{
   571  			"audit-log-exclude-methods": []int{2, 3, 4},
   572  		},
   573  	)
   574  	c.Assert(err, gc.ErrorMatches, `audit-log-exclude-methods\[0\]: expected string, got int\(2\)`)
   575  }
   576  
   577  func (s *ConfigSuite) TestAuditLogFloatBackupsLoadedDirectly(c *gc.C) {
   578  	// We still need to be able to handle floats in data loaded from the DB.
   579  	cfg := controller.Config{
   580  		controller.AuditLogMaxBackups: 10.0,
   581  	}
   582  	c.Assert(cfg.AuditLogMaxBackups(), gc.Equals, 10)
   583  }
   584  
   585  func (s *ConfigSuite) TestConfigManagementSpaceAsConstraint(c *gc.C) {
   586  	managementSpace := "management-space"
   587  	cfg, err := controller.NewConfig(
   588  		testing.ControllerTag.Id(),
   589  		testing.CACert,
   590  		map[string]interface{}{controller.JujuHASpace: managementSpace},
   591  	)
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	c.Check(*cfg.AsSpaceConstraints(nil), gc.DeepEquals, []string{managementSpace})
   594  }
   595  
   596  func (s *ConfigSuite) TestConfigHASpaceAsConstraint(c *gc.C) {
   597  	haSpace := "ha-space"
   598  	cfg, err := controller.NewConfig(
   599  		testing.ControllerTag.Id(),
   600  		testing.CACert,
   601  		map[string]interface{}{controller.JujuHASpace: haSpace},
   602  	)
   603  	c.Assert(err, jc.ErrorIsNil)
   604  	c.Check(*cfg.AsSpaceConstraints(nil), gc.DeepEquals, []string{haSpace})
   605  }
   606  
   607  func (s *ConfigSuite) TestConfigAllSpacesAsMergedConstraints(c *gc.C) {
   608  	haSpace := "ha-space"
   609  	managementSpace := "management-space"
   610  	constraintSpace := "constraint-space"
   611  
   612  	cfg, err := controller.NewConfig(
   613  		testing.ControllerTag.Id(),
   614  		testing.CACert,
   615  		map[string]interface{}{
   616  			controller.JujuHASpace:         haSpace,
   617  			controller.JujuManagementSpace: managementSpace,
   618  		},
   619  	)
   620  	c.Assert(err, jc.ErrorIsNil)
   621  
   622  	got := *cfg.AsSpaceConstraints(&[]string{constraintSpace})
   623  	c.Check(got, gc.DeepEquals, []string{constraintSpace, haSpace, managementSpace})
   624  }
   625  
   626  func (s *ConfigSuite) TestConfigNoSpacesNilSpaceConfigPreserved(c *gc.C) {
   627  	cfg, err := controller.NewConfig(
   628  		testing.ControllerTag.Id(),
   629  		testing.CACert,
   630  		map[string]interface{}{},
   631  	)
   632  	c.Assert(err, jc.ErrorIsNil)
   633  	c.Check(cfg.AsSpaceConstraints(nil), gc.IsNil)
   634  }
   635  
   636  func (s *ConfigSuite) TestCAASImageRepo(c *gc.C) {
   637  	ctrl := gomock.NewController(c)
   638  	defer ctrl.Finish()
   639  
   640  	// Ensure no requests are made from controller config code.
   641  	mockRoundTripper := mocks.NewMockRoundTripper(ctrl)
   642  	s.PatchValue(&registry.DefaultTransport, mockRoundTripper)
   643  
   644  	type tc struct {
   645  		content  string
   646  		expected string
   647  	}
   648  	for _, imageRepo := range []tc{
   649  		//used to reset since we don't have a --reset option
   650  		{content: "", expected: ""},
   651  		{content: "docker.io/juju-operator-repo", expected: ""},
   652  		{content: "registry.foo.com/jujuqa", expected: ""},
   653  		{content: "ghcr.io/jujuqa", expected: ""},
   654  		{content: "registry.gitlab.com/jujuqa", expected: ""},
   655  		{
   656  			content: fmt.Sprintf(`
   657  {
   658      "serveraddress": "ghcr.io",
   659      "auth": "%s",
   660      "repository": "ghcr.io/test-account"
   661  }`, base64.StdEncoding.EncodeToString([]byte("username:pwd"))),
   662  			expected: "ghcr.io/test-account"},
   663  	} {
   664  		c.Logf("testing %#v", imageRepo)
   665  		if imageRepo.expected == "" {
   666  			imageRepo.expected = imageRepo.content
   667  		}
   668  		cfg, err := controller.NewConfig(
   669  			testing.ControllerTag.Id(),
   670  			testing.CACert,
   671  			map[string]interface{}{
   672  				controller.CAASImageRepo: imageRepo.content,
   673  			},
   674  		)
   675  		c.Check(err, jc.ErrorIsNil)
   676  		imageRepoDetails, err := docker.NewImageRepoDetails(cfg.CAASImageRepo())
   677  		c.Check(err, jc.ErrorIsNil)
   678  		c.Check(imageRepoDetails.Repository, gc.Equals, imageRepo.expected)
   679  	}
   680  }
   681  
   682  func (s *ConfigSuite) TestControllerNameDefault(c *gc.C) {
   683  	cfg := controller.Config{}
   684  	c.Check(cfg.ControllerName(), gc.Equals, "")
   685  }
   686  
   687  func (s *ConfigSuite) TestControllerNameSetGet(c *gc.C) {
   688  	cfg, err := controller.NewConfig(
   689  		testing.ControllerTag.Id(),
   690  		testing.CACert,
   691  		map[string]interface{}{
   692  			controller.ControllerName: "test",
   693  		},
   694  	)
   695  	c.Assert(err, jc.ErrorIsNil)
   696  	c.Check(cfg.ControllerName(), gc.Equals, "test")
   697  }
   698  
   699  func (s *ConfigSuite) TestMeteringURLDefault(c *gc.C) {
   700  	cfg, err := controller.NewConfig(
   701  		testing.ControllerTag.Id(),
   702  		testing.CACert,
   703  		map[string]interface{}{},
   704  	)
   705  	c.Assert(err, jc.ErrorIsNil)
   706  	c.Check(cfg.MeteringURL(), gc.Equals, romulus.DefaultAPIRoot)
   707  }
   708  
   709  func (s *ConfigSuite) TestMeteringURLSettingValue(c *gc.C) {
   710  	mURL := "http://homestarrunner.com/metering"
   711  	cfg, err := controller.NewConfig(
   712  		testing.ControllerTag.Id(),
   713  		testing.CACert,
   714  		map[string]interface{}{
   715  			controller.MeteringURL: mURL,
   716  		},
   717  	)
   718  	c.Assert(err, jc.ErrorIsNil)
   719  	c.Assert(cfg.MeteringURL(), gc.Equals, mURL)
   720  }
   721  
   722  func (s *ConfigSuite) TestMaxDebugLogDuration(c *gc.C) {
   723  	cfg, err := controller.NewConfig(
   724  		testing.ControllerTag.Id(),
   725  		testing.CACert,
   726  		map[string]interface{}{
   727  			"max-debug-log-duration": "90m",
   728  		},
   729  	)
   730  	c.Assert(err, jc.ErrorIsNil)
   731  	c.Assert(cfg.MaxDebugLogDuration(), gc.Equals, 90*time.Minute)
   732  }
   733  
   734  func (s *ConfigSuite) TestMaxDebugLogDurationSchemaCoerce(c *gc.C) {
   735  	_, err := controller.NewConfig(
   736  		testing.ControllerTag.Id(),
   737  		testing.CACert,
   738  		map[string]interface{}{
   739  			"max-debug-log-duration": "12",
   740  		},
   741  	)
   742  	c.Assert(err, gc.ErrorMatches, `max-debug-log-duration: conversion to duration: time: missing unit in duration "?12"?`)
   743  }
   744  
   745  func (s *ConfigSuite) TestFeatureFlags(c *gc.C) {
   746  	cfg, err := controller.NewConfig(
   747  		testing.ControllerTag.Id(),
   748  		testing.CACert,
   749  		map[string]interface{}{
   750  			controller.Features: `["foo","bar"]`,
   751  		},
   752  	)
   753  	c.Assert(err, jc.ErrorIsNil)
   754  	c.Check(cfg.Features().Values(), jc.SameContents, []string{"foo", "bar"})
   755  }
   756  
   757  func (s *ConfigSuite) TestDefaults(c *gc.C) {
   758  	cfg, err := controller.NewConfig(
   759  		testing.ControllerTag.Id(),
   760  		testing.CACert,
   761  		map[string]interface{}{},
   762  	)
   763  	c.Assert(err, jc.ErrorIsNil)
   764  	c.Assert(cfg.AgentRateLimitMax(), gc.Equals, controller.DefaultAgentRateLimitMax)
   765  	c.Assert(cfg.AgentRateLimitRate(), gc.Equals, controller.DefaultAgentRateLimitRate)
   766  	c.Assert(cfg.MaxDebugLogDuration(), gc.Equals, controller.DefaultMaxDebugLogDuration)
   767  	c.Assert(cfg.AgentLogfileMaxBackups(), gc.Equals, controller.DefaultAgentLogfileMaxBackups)
   768  	c.Assert(cfg.AgentLogfileMaxSizeMB(), gc.Equals, controller.DefaultAgentLogfileMaxSize)
   769  	c.Assert(cfg.ModelLogfileMaxBackups(), gc.Equals, controller.DefaultModelLogfileMaxBackups)
   770  	c.Assert(cfg.ModelLogfileMaxSizeMB(), gc.Equals, controller.DefaultModelLogfileMaxSize)
   771  	c.Assert(cfg.ApplicationResourceDownloadLimit(), gc.Equals, controller.DefaultApplicationResourceDownloadLimit)
   772  	c.Assert(cfg.ControllerResourceDownloadLimit(), gc.Equals, controller.DefaultControllerResourceDownloadLimit)
   773  	c.Assert(cfg.QueryTracingEnabled(), gc.Equals, controller.DefaultQueryTracingEnabled)
   774  	c.Assert(cfg.QueryTracingThreshold(), gc.Equals, controller.DefaultQueryTracingThreshold)
   775  }
   776  
   777  func (s *ConfigSuite) TestAgentLogfile(c *gc.C) {
   778  	cfg, err := controller.NewConfig(
   779  		testing.ControllerTag.Id(),
   780  		testing.CACert,
   781  		map[string]interface{}{
   782  			"agent-logfile-max-size":    "35M",
   783  			"agent-logfile-max-backups": "17",
   784  		},
   785  	)
   786  	c.Assert(err, jc.ErrorIsNil)
   787  	c.Assert(cfg.AgentLogfileMaxBackups(), gc.Equals, 17)
   788  	c.Assert(cfg.AgentLogfileMaxSizeMB(), gc.Equals, 35)
   789  }
   790  
   791  func (s *ConfigSuite) TestAgentLogfileBackupErr(c *gc.C) {
   792  	_, err := controller.NewConfig(
   793  		testing.ControllerTag.Id(),
   794  		testing.CACert,
   795  		map[string]interface{}{
   796  			"agent-logfile-max-backups": "two",
   797  		},
   798  	)
   799  	c.Assert(err.Error(), gc.Equals, `agent-logfile-max-backups: expected number, got string("two")`)
   800  }
   801  
   802  func (s *ConfigSuite) TestModelLogfile(c *gc.C) {
   803  	cfg, err := controller.NewConfig(
   804  		testing.ControllerTag.Id(),
   805  		testing.CACert,
   806  		map[string]interface{}{
   807  			"model-logfile-max-size":    "25M",
   808  			"model-logfile-max-backups": "15",
   809  		},
   810  	)
   811  	c.Assert(err, jc.ErrorIsNil)
   812  	c.Assert(cfg.ModelLogfileMaxBackups(), gc.Equals, 15)
   813  	c.Assert(cfg.ModelLogfileMaxSizeMB(), gc.Equals, 25)
   814  }
   815  
   816  func (s *ConfigSuite) TestModelLogfileBackupErr(c *gc.C) {
   817  	_, err := controller.NewConfig(
   818  		testing.ControllerTag.Id(),
   819  		testing.CACert,
   820  		map[string]interface{}{
   821  			"model-logfile-max-backups": "two",
   822  		},
   823  	)
   824  	c.Assert(err.Error(), gc.Equals, `model-logfile-max-backups: expected number, got string("two")`)
   825  }
   826  
   827  func (s *ConfigSuite) TestAgentRateLimitMax(c *gc.C) {
   828  	cfg, err := controller.NewConfig(
   829  		testing.ControllerTag.Id(),
   830  		testing.CACert,
   831  		map[string]interface{}{
   832  			"agent-ratelimit-max": "0",
   833  		},
   834  	)
   835  	c.Assert(err, jc.ErrorIsNil)
   836  	c.Assert(cfg.AgentRateLimitMax(), gc.Equals, 0)
   837  }
   838  
   839  func (s *ConfigSuite) TestAgentRateLimitRate(c *gc.C) {
   840  	cfg, err := controller.NewConfig(
   841  		testing.ControllerTag.Id(),
   842  		testing.CACert, nil)
   843  	c.Assert(err, jc.ErrorIsNil)
   844  	c.Assert(cfg.AgentRateLimitRate(), gc.Equals, controller.DefaultAgentRateLimitRate)
   845  
   846  	cfg[controller.AgentRateLimitRate] = time.Second
   847  	c.Assert(cfg.AgentRateLimitRate(), gc.Equals, time.Second)
   848  
   849  	cfg[controller.AgentRateLimitRate] = "500ms"
   850  	c.Assert(cfg.AgentRateLimitRate(), gc.Equals, 500*time.Millisecond)
   851  }
   852  
   853  func (s *ConfigSuite) TestJujuDBSnapChannel(c *gc.C) {
   854  	cfg, err := controller.NewConfig(
   855  		testing.ControllerTag.Id(),
   856  		testing.CACert,
   857  		map[string]interface{}{},
   858  	)
   859  	c.Assert(err, jc.ErrorIsNil)
   860  	c.Assert(cfg.JujuDBSnapChannel(), gc.Equals, controller.DefaultJujuDBSnapChannel)
   861  
   862  	cfg, err = controller.NewConfig(
   863  		testing.ControllerTag.Id(),
   864  		testing.CACert,
   865  		map[string]interface{}{
   866  			"juju-db-snap-channel": "latest/candidate",
   867  		},
   868  	)
   869  	c.Assert(err, jc.ErrorIsNil)
   870  	c.Assert(cfg.JujuDBSnapChannel(), gc.Equals, "latest/candidate")
   871  }
   872  
   873  func (s *ConfigSuite) TestMigrationMinionWaitMax(c *gc.C) {
   874  	cfg, err := controller.NewConfig(
   875  		testing.ControllerTag.Id(),
   876  		testing.CACert, nil)
   877  	c.Assert(err, jc.ErrorIsNil)
   878  
   879  	c.Assert(cfg.MigrationMinionWaitMax(), gc.Equals, controller.DefaultMigrationMinionWaitMax)
   880  
   881  	cfg[controller.MigrationMinionWaitMax] = "500ms"
   882  	c.Assert(cfg.MigrationMinionWaitMax(), gc.Equals, 500*time.Millisecond)
   883  }
   884  
   885  func (s *ConfigSuite) TestQueryTraceEnabled(c *gc.C) {
   886  	cfg, err := controller.NewConfig(
   887  		testing.ControllerTag.Id(),
   888  		testing.CACert, nil)
   889  	c.Assert(err, jc.ErrorIsNil)
   890  
   891  	c.Assert(cfg.QueryTracingEnabled(), gc.Equals, controller.DefaultQueryTracingEnabled)
   892  
   893  	cfg[controller.QueryTracingEnabled] = true
   894  	c.Assert(cfg.QueryTracingEnabled(), gc.Equals, true)
   895  }
   896  
   897  func (s *ConfigSuite) TestQueryTraceThreshold(c *gc.C) {
   898  	cfg, err := controller.NewConfig(
   899  		testing.ControllerTag.Id(),
   900  		testing.CACert, nil)
   901  	c.Assert(err, jc.ErrorIsNil)
   902  
   903  	c.Assert(cfg.QueryTracingThreshold(), gc.Equals, controller.DefaultQueryTracingThreshold)
   904  
   905  	cfg[controller.QueryTracingThreshold] = time.Second * 10
   906  	c.Assert(cfg.QueryTracingThreshold(), gc.Equals, time.Second*10)
   907  
   908  	d := time.Second * 10
   909  	cfg[controller.QueryTracingThreshold] = d.String()
   910  
   911  	bytes, err := json.Marshal(cfg)
   912  	c.Assert(err, jc.ErrorIsNil)
   913  
   914  	var cfg2 controller.Config
   915  	err = json.Unmarshal(bytes, &cfg2)
   916  	c.Assert(err, jc.ErrorIsNil)
   917  
   918  	c.Assert(cfg2.QueryTracingThreshold(), gc.Equals, time.Second*10)
   919  }