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

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"strings"
     8  
     9  	"github.com/juju/collections/set"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/names/v5"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils/v3"
    14  	"github.com/juju/version/v2"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/cloud"
    18  	"github.com/juju/juju/core/constraints"
    19  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/state"
    22  	statetesting "github.com/juju/juju/state/testing"
    23  	"github.com/juju/juju/storage"
    24  	"github.com/juju/juju/testing"
    25  )
    26  
    27  type ModelConfigSuite struct {
    28  	ConnSuite
    29  }
    30  
    31  var _ = gc.Suite(&ModelConfigSuite{})
    32  
    33  func (s *ModelConfigSuite) SetUpTest(c *gc.C) {
    34  	s.ControllerInheritedConfig = map[string]interface{}{
    35  		"apt-mirror": "http://cloud-mirror",
    36  	}
    37  	s.RegionConfig = cloud.RegionConfig{
    38  		"nether-region": cloud.Attrs{
    39  			"apt-mirror": "http://nether-region-mirror",
    40  			"no-proxy":   "nether-proxy",
    41  		},
    42  		"dummy-region": cloud.Attrs{
    43  			"no-proxy":     "dummy-proxy",
    44  			"image-stream": "dummy-image-stream",
    45  			"whimsy-key":   "whimsy-value",
    46  		},
    47  	}
    48  	s.ConnSuite.SetUpTest(c)
    49  	s.policy.GetConstraintsValidator = func() (constraints.Validator, error) {
    50  		validator := constraints.NewValidator()
    51  		validator.RegisterConflicts([]string{constraints.InstanceType}, []string{constraints.Mem})
    52  		validator.RegisterUnsupported([]string{constraints.CpuPower})
    53  		return validator, nil
    54  	}
    55  	s.policy.GetProviderConfigSchemaSource = func(cloudName string) (config.ConfigSchemaSource, error) {
    56  		return &statetesting.MockConfigSchemaSource{CloudName: cloudName}, nil
    57  	}
    58  }
    59  
    60  func (s *ModelConfigSuite) TestAdditionalValidation(c *gc.C) {
    61  	updateAttrs := map[string]interface{}{"logging-config": "juju=ERROR"}
    62  	configValidator1 := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
    63  		c.Assert(updateAttrs, jc.DeepEquals, map[string]interface{}{"logging-config": "juju=ERROR"})
    64  		if lc, found := updateAttrs["logging-config"]; found && lc != "" {
    65  			return errors.New("cannot change logging-config")
    66  		}
    67  		return nil
    68  	}
    69  	removeAttrs := []string{"some-attr"}
    70  	configValidator2 := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
    71  		c.Assert(removeAttrs, jc.DeepEquals, []string{"some-attr"})
    72  		for _, i := range removeAttrs {
    73  			if i == "some-attr" {
    74  				return errors.New("cannot remove some-attr")
    75  			}
    76  		}
    77  		return nil
    78  	}
    79  	configValidator3 := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
    80  		return nil
    81  	}
    82  
    83  	err := s.Model.UpdateModelConfig(updateAttrs, nil, configValidator1)
    84  	c.Assert(err, gc.ErrorMatches, "cannot change logging-config")
    85  	err = s.Model.UpdateModelConfig(nil, removeAttrs, configValidator2)
    86  	c.Assert(err, gc.ErrorMatches, "cannot remove some-attr")
    87  	err = s.Model.UpdateModelConfig(updateAttrs, nil, configValidator3)
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	// First error is returned.
    90  	err = s.Model.UpdateModelConfig(updateAttrs, nil, configValidator1, configValidator2)
    91  	c.Assert(err, gc.ErrorMatches, "cannot change logging-config")
    92  }
    93  
    94  func (s *ModelConfigSuite) TestModelConfig(c *gc.C) {
    95  	attrs := map[string]interface{}{
    96  		"authorized-keys": "different-keys",
    97  		"arbitrary-key":   "shazam!",
    98  	}
    99  	cfg, err := s.Model.ModelConfig()
   100  	c.Assert(err, jc.ErrorIsNil)
   101  	err = s.Model.UpdateModelConfig(attrs, nil)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  	cfg, err = cfg.Apply(attrs)
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	oldCfg, err := s.Model.ModelConfig()
   106  	c.Assert(err, jc.ErrorIsNil)
   107  
   108  	c.Assert(oldCfg, jc.DeepEquals, cfg)
   109  }
   110  
   111  func (s *ModelConfigSuite) TestAgentVersion(c *gc.C) {
   112  	attrs := map[string]interface{}{
   113  		"agent-version": "2.2.3",
   114  		"arbitrary-key": "shazam!",
   115  	}
   116  	ver, err := s.Model.AgentVersion()
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	c.Assert(ver, gc.DeepEquals, version.Number{Major: 2, Minor: 0, Patch: 0})
   119  
   120  	err = s.Model.UpdateModelConfig(attrs, nil)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  
   123  	ver, err = s.Model.AgentVersion()
   124  	c.Assert(err, jc.ErrorIsNil)
   125  	c.Assert(ver, gc.DeepEquals, version.Number{Major: 2, Minor: 2, Patch: 3})
   126  }
   127  
   128  func (s *ModelConfigSuite) TestComposeNewModelConfig(c *gc.C) {
   129  	attrs := map[string]interface{}{
   130  		"authorized-keys": "different-keys",
   131  		"arbitrary-key":   "shazam!",
   132  		"uuid":            testing.ModelTag.Id(),
   133  		"type":            "dummy",
   134  		"name":            "test",
   135  		"resource-tags":   map[string]string{"a": "b", "c": "d"},
   136  	}
   137  
   138  	cfgAttrs, err := s.State.ComposeNewModelConfig(
   139  		attrs, &environscloudspec.CloudRegionSpec{
   140  			Cloud:  "dummy",
   141  			Region: "dummy-region"})
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	expectedCfg, err := config.New(config.UseDefaults, attrs)
   144  	c.Assert(err, jc.ErrorIsNil)
   145  	expected := expectedCfg.AllAttrs()
   146  	expected["apt-mirror"] = "http://cloud-mirror"
   147  	expected["providerAttrdummy"] = "vulch"
   148  	expected["whimsy-key"] = "whimsy-value"
   149  	expected["image-stream"] = "dummy-image-stream"
   150  	expected["no-proxy"] = "dummy-proxy"
   151  	// config.New() adds logging-config so remove it.
   152  	expected["logging-config"] = ""
   153  	c.Assert(cfgAttrs, jc.DeepEquals, expected)
   154  }
   155  
   156  func (s *ModelConfigSuite) TestComposeNewModelConfigRegionMisses(c *gc.C) {
   157  	attrs := map[string]interface{}{
   158  		"authorized-keys": "different-keys",
   159  		"arbitrary-key":   "shazam!",
   160  		"uuid":            testing.ModelTag.Id(),
   161  		"type":            "dummy",
   162  		"name":            "test",
   163  		"resource-tags":   map[string]string{"a": "b", "c": "d"},
   164  	}
   165  	rspec := &environscloudspec.CloudRegionSpec{Cloud: "dummy", Region: "dummy-region"}
   166  	cfgAttrs, err := s.State.ComposeNewModelConfig(attrs, rspec)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	expectedCfg, err := config.New(config.UseDefaults, attrs)
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	expected := expectedCfg.AllAttrs()
   171  	expected["apt-mirror"] = "http://cloud-mirror"
   172  	expected["providerAttrdummy"] = "vulch"
   173  	expected["whimsy-key"] = "whimsy-value"
   174  	expected["no-proxy"] = "dummy-proxy"
   175  	expected["image-stream"] = "dummy-image-stream"
   176  	// config.New() adds logging-config so remove it.
   177  	expected["logging-config"] = ""
   178  	c.Assert(cfgAttrs, jc.DeepEquals, expected)
   179  }
   180  
   181  func (s *ModelConfigSuite) TestComposeNewModelConfigRegionInherits(c *gc.C) {
   182  	attrs := map[string]interface{}{
   183  		"authorized-keys": "different-keys",
   184  		"arbitrary-key":   "shazam!",
   185  		"uuid":            testing.ModelTag.Id(),
   186  		"type":            "dummy",
   187  		"name":            "test",
   188  		"resource-tags":   map[string]string{"a": "b", "c": "d"},
   189  	}
   190  	rspec := &environscloudspec.CloudRegionSpec{Cloud: "dummy", Region: "nether-region"}
   191  	cfgAttrs, err := s.State.ComposeNewModelConfig(attrs, rspec)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  	expectedCfg, err := config.New(config.UseDefaults, attrs)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  	expected := expectedCfg.AllAttrs()
   196  	expected["no-proxy"] = "nether-proxy"
   197  	expected["apt-mirror"] = "http://nether-region-mirror"
   198  	expected["providerAttrdummy"] = "vulch"
   199  	// config.New() adds logging-config so remove it.
   200  	expected["logging-config"] = ""
   201  	c.Assert(cfgAttrs, jc.DeepEquals, expected)
   202  }
   203  
   204  func (s *ModelConfigSuite) TestUpdateModelConfigRejectsControllerConfig(c *gc.C) {
   205  	updateAttrs := map[string]interface{}{"api-port": 1234}
   206  	err := s.Model.UpdateModelConfig(updateAttrs, nil)
   207  	c.Assert(err, gc.ErrorMatches, `cannot set controller attribute "api-port" on a model`)
   208  }
   209  
   210  func (s *ModelConfigSuite) TestUpdateModelConfigRemoveInherited(c *gc.C) {
   211  	attrs := map[string]interface{}{
   212  		"apt-mirror":        "http://different-mirror", // controller
   213  		"arbitrary-key":     "shazam!",
   214  		"providerAttrdummy": "beef", // provider
   215  		"whimsy-key":        "eggs", // region
   216  	}
   217  	err := s.Model.UpdateModelConfig(attrs, nil)
   218  	c.Assert(err, jc.ErrorIsNil)
   219  
   220  	err = s.Model.UpdateModelConfig(nil, []string{"apt-mirror", "arbitrary-key", "providerAttrdummy", "whimsy-key"})
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	cfg, err := s.Model.ModelConfig()
   223  	c.Assert(err, jc.ErrorIsNil)
   224  	allAttrs := cfg.AllAttrs()
   225  	c.Assert(allAttrs["apt-mirror"], gc.Equals, "http://cloud-mirror")
   226  	c.Assert(allAttrs["providerAttrdummy"], gc.Equals, "vulch")
   227  	c.Assert(allAttrs["whimsy-key"], gc.Equals, "whimsy-value")
   228  	_, ok := allAttrs["arbitrary-key"]
   229  	c.Assert(ok, jc.IsFalse)
   230  }
   231  
   232  func (s *ModelConfigSuite) TestUpdateModelConfigCoerce(c *gc.C) {
   233  	attrs := map[string]interface{}{
   234  		"resource-tags": map[string]string{"a": "b", "c": "d"},
   235  	}
   236  	err := s.Model.UpdateModelConfig(attrs, nil)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  
   239  	modelSettings, err := s.State.ReadSettings(state.SettingsC, state.ModelGlobalKey)
   240  	c.Assert(err, jc.ErrorIsNil)
   241  	expectedTags := map[string]string{"a": "b", "c": "d"}
   242  	tagsStr := config.CoerceForStorage(modelSettings.Map())["resource-tags"].(string)
   243  	tagItems := strings.Split(tagsStr, " ")
   244  	tagsMap := make(map[string]string)
   245  	for _, kv := range tagItems {
   246  		parts := strings.Split(kv, "=")
   247  		tagsMap[parts[0]] = parts[1]
   248  	}
   249  	c.Assert(tagsMap, gc.DeepEquals, expectedTags)
   250  
   251  	cfg, err := s.Model.ModelConfig()
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(cfg.AllAttrs()["resource-tags"], gc.DeepEquals, expectedTags)
   254  }
   255  
   256  func (s *ModelConfigSuite) TestUpdateModelConfigPreferredOverRemove(c *gc.C) {
   257  	attrs := map[string]interface{}{
   258  		"apt-mirror":        "http://different-mirror", // controller
   259  		"arbitrary-key":     "shazam!",
   260  		"providerAttrdummy": "beef", // provider
   261  	}
   262  	err := s.Model.UpdateModelConfig(attrs, nil)
   263  	c.Assert(err, jc.ErrorIsNil)
   264  
   265  	err = s.Model.UpdateModelConfig(map[string]interface{}{
   266  		"apt-mirror":        "http://another-mirror",
   267  		"providerAttrdummy": "pork",
   268  	}, []string{"apt-mirror", "arbitrary-key"})
   269  	c.Assert(err, jc.ErrorIsNil)
   270  	cfg, err := s.Model.ModelConfig()
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	allAttrs := cfg.AllAttrs()
   273  	c.Assert(allAttrs["apt-mirror"], gc.Equals, "http://another-mirror")
   274  	c.Assert(allAttrs["providerAttrdummy"], gc.Equals, "pork")
   275  	_, ok := allAttrs["arbitrary-key"]
   276  	c.Assert(ok, jc.IsFalse)
   277  }
   278  
   279  type ModelConfigSourceSuite struct {
   280  	ConnSuite
   281  }
   282  
   283  var _ = gc.Suite(&ModelConfigSourceSuite{})
   284  
   285  func (s *ModelConfigSourceSuite) SetUpTest(c *gc.C) {
   286  	s.ControllerInheritedConfig = map[string]interface{}{
   287  		"apt-mirror": "http://cloud-mirror",
   288  		"http-proxy": "http://proxy",
   289  	}
   290  	s.RegionConfig = cloud.RegionConfig{
   291  		"dummy-region": cloud.Attrs{
   292  			"apt-mirror": "http://dummy-mirror",
   293  			"no-proxy":   "dummy-proxy",
   294  		},
   295  	}
   296  	s.ConnSuite.SetUpTest(c)
   297  
   298  	localControllerSettings, err := s.State.ReadSettings(state.GlobalSettingsC, state.CloudGlobalKey("dummy"))
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	localControllerSettings.Set("apt-mirror", "http://mirror")
   301  	_, err = localControllerSettings.Write()
   302  	c.Assert(err, jc.ErrorIsNil)
   303  }
   304  
   305  func (s *ModelConfigSourceSuite) TestModelConfigWhenSetOverridesControllerValue(c *gc.C) {
   306  	attrs := map[string]interface{}{
   307  		"authorized-keys": "different-keys",
   308  		"apt-mirror":      "http://anothermirror",
   309  	}
   310  	err := s.Model.UpdateModelConfig(attrs, nil)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  
   313  	cfg, err := s.Model.ModelConfig()
   314  	c.Assert(err, jc.ErrorIsNil)
   315  	c.Assert(cfg.AllAttrs()["apt-mirror"], gc.Equals, "http://anothermirror")
   316  }
   317  
   318  func (s *ModelConfigSourceSuite) TestControllerModelConfigForksControllerValue(c *gc.C) {
   319  	modelCfg, err := s.Model.ModelConfig()
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	c.Assert(modelCfg.AllAttrs()["apt-mirror"], gc.Equals, "http://cloud-mirror")
   322  
   323  	// Change the local controller settings and ensure the model setting stays the same.
   324  	localControllerSettings, err := s.State.ReadSettings(state.GlobalSettingsC, state.CloudGlobalKey("dummy"))
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	localControllerSettings.Set("apt-mirror", "http://anothermirror")
   327  	_, err = localControllerSettings.Write()
   328  	c.Assert(err, jc.ErrorIsNil)
   329  
   330  	modelCfg, err = s.Model.ModelConfig()
   331  	c.Assert(err, jc.ErrorIsNil)
   332  	c.Assert(modelCfg.AllAttrs()["apt-mirror"], gc.Equals, "http://cloud-mirror")
   333  }
   334  
   335  func (s *ModelConfigSourceSuite) TestNewModelConfigForksControllerValue(c *gc.C) {
   336  	uuid, err := utils.NewUUID()
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	cfg := testing.CustomModelConfig(c, testing.Attrs{
   339  		"name": "another",
   340  		"uuid": uuid.String(),
   341  	})
   342  	owner := names.NewUserTag("test@remote")
   343  	_, st, err := s.Controller.NewModel(state.ModelArgs{
   344  		Type:                    state.ModelTypeIAAS,
   345  		Config:                  cfg,
   346  		Owner:                   owner,
   347  		CloudName:               "dummy",
   348  		CloudRegion:             "nether-region",
   349  		StorageProviderRegistry: storage.StaticProviderRegistry{},
   350  	})
   351  	c.Assert(err, jc.ErrorIsNil)
   352  	defer st.Close()
   353  
   354  	m, err := st.Model()
   355  	c.Assert(err, jc.ErrorIsNil)
   356  
   357  	modelCfg, err := m.ModelConfig()
   358  	c.Assert(err, jc.ErrorIsNil)
   359  	c.Assert(modelCfg.AllAttrs()["apt-mirror"], gc.Equals, "http://mirror")
   360  
   361  	// Change the local controller settings and ensure the model setting stays the same.
   362  	localCloudSettings, err := s.State.ReadSettings(state.GlobalSettingsC, state.CloudGlobalKey("dummy"))
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	localCloudSettings.Set("apt-mirror", "http://anothermirror")
   365  	_, err = localCloudSettings.Write()
   366  	c.Assert(err, jc.ErrorIsNil)
   367  
   368  	modelCfg, err = m.ModelConfig()
   369  	c.Assert(err, jc.ErrorIsNil)
   370  	c.Assert(modelCfg.AllAttrs()["apt-mirror"], gc.Equals, "http://mirror")
   371  }
   372  
   373  func (s *ModelConfigSourceSuite) assertModelConfigValues(c *gc.C, modelCfg *config.Config, modelAttributes, controllerAttributes set.Strings) {
   374  	expectedValues := make(config.ConfigValues)
   375  	defaultAttributes := set.NewStrings()
   376  	for defaultAttr := range config.ConfigDefaults() {
   377  		defaultAttributes.Add(defaultAttr)
   378  	}
   379  	for attr, val := range modelCfg.AllAttrs() {
   380  		source := "model"
   381  		if defaultAttributes.Contains(attr) {
   382  			source = "default"
   383  		}
   384  		if modelAttributes.Contains(attr) {
   385  			source = "model"
   386  		}
   387  		if controllerAttributes.Contains(attr) {
   388  			source = "controller"
   389  		}
   390  		expectedValues[attr] = config.ConfigValue{
   391  			Value:  val,
   392  			Source: source,
   393  		}
   394  	}
   395  	sources, err := s.Model.ModelConfigValues()
   396  	c.Assert(err, jc.ErrorIsNil)
   397  	c.Assert(sources, jc.DeepEquals, expectedValues)
   398  }
   399  
   400  func (s *ModelConfigSourceSuite) TestModelConfigValues(c *gc.C) {
   401  	modelCfg, err := s.Model.ModelConfig()
   402  	c.Assert(err, jc.ErrorIsNil)
   403  	modelAttributes := set.NewStrings("name", "apt-mirror", "logging-config", "authorized-keys", "resource-tags")
   404  	s.assertModelConfigValues(c, modelCfg, modelAttributes, set.NewStrings("http-proxy"))
   405  }
   406  
   407  func (s *ModelConfigSourceSuite) TestModelConfigUpdateSource(c *gc.C) {
   408  	attrs := map[string]interface{}{
   409  		"http-proxy": "http://anotherproxy",
   410  		"apt-mirror": "http://mirror",
   411  	}
   412  	err := s.Model.UpdateModelConfig(attrs, nil)
   413  	c.Assert(err, jc.ErrorIsNil)
   414  	modelCfg, err := s.Model.ModelConfig()
   415  	c.Assert(err, jc.ErrorIsNil)
   416  	modelAttributes := set.NewStrings("name", "http-proxy", "logging-config", "authorized-keys", "resource-tags")
   417  	s.assertModelConfigValues(c, modelCfg, modelAttributes, set.NewStrings("apt-mirror"))
   418  }
   419  
   420  func (s *ModelConfigSourceSuite) TestModelConfigDefaults(c *gc.C) {
   421  	expectedValues := make(config.ModelDefaultAttributes)
   422  	for attr, val := range config.ConfigDefaults() {
   423  		expectedValues[attr] = config.AttributeDefaultValues{
   424  			Default: val,
   425  		}
   426  	}
   427  	ds := expectedValues["http-proxy"]
   428  	ds.Controller = "http://proxy"
   429  	expectedValues["http-proxy"] = ds
   430  
   431  	ds = expectedValues["apt-mirror"]
   432  	ds.Controller = "http://mirror"
   433  	ds.Regions = []config.RegionDefaultValue{{
   434  		Name:  "dummy-region",
   435  		Value: "http://dummy-mirror",
   436  	}}
   437  	expectedValues["apt-mirror"] = ds
   438  
   439  	ds = expectedValues["no-proxy"]
   440  	ds.Regions = []config.RegionDefaultValue{{
   441  		Name:  "dummy-region",
   442  		Value: "dummy-proxy"}}
   443  	expectedValues["no-proxy"] = ds
   444  
   445  	sources, err := s.State.ModelConfigDefaultValues(s.Model.CloudName())
   446  	c.Assert(err, jc.ErrorIsNil)
   447  	c.Assert(sources, jc.DeepEquals, expectedValues)
   448  }
   449  
   450  func (s *ModelConfigSourceSuite) TestUpdateModelConfigDefaults(c *gc.C) {
   451  	// Set up values that will be removed.
   452  	attrs := map[string]interface{}{
   453  		"http-proxy":  "http://http-proxy",
   454  		"https-proxy": "https://https-proxy",
   455  	}
   456  	err := s.State.UpdateModelConfigDefaultValues(attrs, nil, nil)
   457  	c.Assert(err, jc.ErrorIsNil)
   458  
   459  	attrs = map[string]interface{}{
   460  		"apt-mirror":            "http://different-mirror",
   461  		"num-provision-workers": 66,
   462  	}
   463  	err = s.State.UpdateModelConfigDefaultValues(attrs, []string{"http-proxy", "https-proxy"}, nil)
   464  	c.Assert(err, jc.ErrorIsNil)
   465  
   466  	cfg, err := s.State.ModelConfigDefaultValues(s.Model.CloudName())
   467  	c.Assert(err, jc.ErrorIsNil)
   468  	expectedValues := make(config.ModelDefaultAttributes)
   469  	for attr, val := range config.ConfigDefaults() {
   470  		expectedValues[attr] = config.AttributeDefaultValues{
   471  			Default: val,
   472  		}
   473  	}
   474  	delete(expectedValues, "http-mirror")
   475  	delete(expectedValues, "https-mirror")
   476  	expectedValues["apt-mirror"] = config.AttributeDefaultValues{
   477  		Controller: "http://different-mirror",
   478  		Default:    "",
   479  		Regions: []config.RegionDefaultValue{{
   480  			Name:  "dummy-region",
   481  			Value: "http://dummy-mirror",
   482  		}}}
   483  	expectedValues["no-proxy"] = config.AttributeDefaultValues{
   484  		Default: "127.0.0.1,localhost,::1",
   485  		Regions: []config.RegionDefaultValue{{
   486  			Name:  "dummy-region",
   487  			Value: "dummy-proxy",
   488  		}}}
   489  	expectedValues["num-provision-workers"] = config.AttributeDefaultValues{
   490  		Controller: 66,
   491  		Default:    16,
   492  	}
   493  	c.Assert(cfg, jc.DeepEquals, expectedValues)
   494  }
   495  
   496  func (s *ModelConfigSourceSuite) TestUpdateModelConfigDefaultsArbitraryConfig(c *gc.C) {
   497  	attrs := map[string]interface{}{
   498  		"hello": "world",
   499  	}
   500  	err := s.State.UpdateModelConfigDefaultValues(attrs, nil, nil)
   501  	c.Assert(err, jc.ErrorIsNil)
   502  
   503  	cfg, err := s.State.ModelConfigDefaultValues(s.Model.CloudName())
   504  	c.Assert(err, jc.ErrorIsNil)
   505  	expectedValues := make(config.ModelDefaultAttributes)
   506  	for attr, val := range config.ConfigDefaults() {
   507  		expectedValues[attr] = config.AttributeDefaultValues{
   508  			Default: val,
   509  		}
   510  	}
   511  
   512  	expectedValues["hello"] = config.AttributeDefaultValues{
   513  		Controller: "world",
   514  		Default:    nil,
   515  	}
   516  	expectedValues["http-proxy"] = config.AttributeDefaultValues{
   517  		Controller: "http://proxy",
   518  		Default:    "",
   519  	}
   520  	expectedValues["apt-mirror"] = config.AttributeDefaultValues{
   521  		Controller: "http://mirror",
   522  		Default:    "",
   523  		Regions: []config.RegionDefaultValue{{
   524  			Name:  "dummy-region",
   525  			Value: "http://dummy-mirror",
   526  		}}}
   527  	expectedValues["no-proxy"] = config.AttributeDefaultValues{
   528  		Default: "127.0.0.1,localhost,::1",
   529  		Regions: []config.RegionDefaultValue{{
   530  			Name:  "dummy-region",
   531  			Value: "dummy-proxy",
   532  		}}}
   533  	c.Assert(cfg, jc.DeepEquals, expectedValues)
   534  }
   535  
   536  func (s *ModelConfigSourceSuite) TestUpdateModelConfigDefaultsWithValidationError(c *gc.C) {
   537  	// Set up values that will be removed.
   538  	attrs := map[string]interface{}{
   539  		"http-proxy":  "http://http-proxy",
   540  		"https-proxy": "https://https-proxy",
   541  	}
   542  	err := s.State.UpdateModelConfigDefaultValues(attrs, nil, nil)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  
   545  	attrs = map[string]interface{}{
   546  		"test-mode": "baz",
   547  	}
   548  	err = s.State.UpdateModelConfigDefaultValues(attrs, []string{"http-proxy", "https-proxy"}, nil)
   549  	c.Assert(err, gc.ErrorMatches, `test-mode: expected bool, got string\("baz"\)`)
   550  }
   551  
   552  func (s *ModelConfigSourceSuite) TestUpdateModelConfigRegionDefaults(c *gc.C) {
   553  	// The test env is setup with dummy/dummy-region having a no-proxy
   554  	// dummy-proxy value and nether-region with a nether-proxy value.
   555  	//
   556  	// First we change the no-proxy setting in dummy-region
   557  	attrs := map[string]interface{}{
   558  		"no-proxy": "changed-proxy",
   559  	}
   560  
   561  	rspec, err := environscloudspec.NewCloudRegionSpec("dummy", "dummy-region")
   562  	c.Assert(err, jc.ErrorIsNil)
   563  
   564  	err = s.State.UpdateModelConfigDefaultValues(attrs, nil, rspec)
   565  	c.Assert(err, jc.ErrorIsNil)
   566  
   567  	cfg, err := s.State.ModelConfigDefaultValues(s.Model.CloudName())
   568  	c.Assert(err, jc.ErrorIsNil)
   569  	expectedValues := make(config.ModelDefaultAttributes)
   570  	for attr, val := range config.ConfigDefaults() {
   571  		expectedValues[attr] = config.AttributeDefaultValues{
   572  			Default: val,
   573  		}
   574  	}
   575  	expectedValues["http-proxy"] = config.AttributeDefaultValues{
   576  		Controller: "http://proxy",
   577  		Default:    "",
   578  	}
   579  	expectedValues["apt-mirror"] = config.AttributeDefaultValues{
   580  		Controller: "http://mirror",
   581  		Default:    "",
   582  		Regions: []config.RegionDefaultValue{{
   583  			Name:  "dummy-region",
   584  			Value: "http://dummy-mirror",
   585  		}}}
   586  	expectedValues["no-proxy"] = config.AttributeDefaultValues{
   587  		Default: "127.0.0.1,localhost,::1",
   588  		Regions: []config.RegionDefaultValue{{
   589  			Name:  "dummy-region",
   590  			Value: "changed-proxy",
   591  		}}}
   592  	c.Assert(cfg, jc.DeepEquals, expectedValues)
   593  
   594  	// remove the dummy-region setting
   595  	err = s.State.UpdateModelConfigDefaultValues(nil, []string{"no-proxy"}, rspec)
   596  	c.Assert(err, jc.ErrorIsNil)
   597  
   598  	// and check again
   599  	cfg, err = s.State.ModelConfigDefaultValues(s.Model.CloudName())
   600  	c.Assert(err, jc.ErrorIsNil)
   601  	expectedValues = make(config.ModelDefaultAttributes)
   602  	for attr, val := range config.ConfigDefaults() {
   603  		expectedValues[attr] = config.AttributeDefaultValues{
   604  			Default: val,
   605  		}
   606  	}
   607  	expectedValues["http-proxy"] = config.AttributeDefaultValues{
   608  		Controller: "http://proxy",
   609  		Default:    "",
   610  	}
   611  	expectedValues["apt-mirror"] = config.AttributeDefaultValues{
   612  		Controller: "http://mirror",
   613  		Default:    "",
   614  		Regions: []config.RegionDefaultValue{{
   615  			Name:  "dummy-region",
   616  			Value: "http://dummy-mirror",
   617  		}}}
   618  	c.Assert(cfg, jc.DeepEquals, expectedValues)
   619  }
   620  
   621  func (s *ModelConfigSourceSuite) TestUpdateModelConfigDefaultValuesUnknownRegion(c *gc.C) {
   622  	// Set up settings to create
   623  	attrs := map[string]interface{}{
   624  		"no-proxy": "changed-proxy",
   625  	}
   626  
   627  	rspec, err := environscloudspec.NewCloudRegionSpec("dummy", "unused-region")
   628  	c.Assert(err, jc.ErrorIsNil)
   629  
   630  	// We add this to the unused-region which has not been created in mongo
   631  	// yet.
   632  	err = s.State.UpdateModelConfigDefaultValues(attrs, nil, rspec)
   633  	c.Assert(err, jc.ErrorIsNil)
   634  
   635  	// Then check config.
   636  	cfg, err := s.State.ModelConfigDefaultValues(s.Model.CloudName())
   637  	c.Assert(err, jc.ErrorIsNil)
   638  	c.Assert(cfg["no-proxy"], jc.DeepEquals, config.AttributeDefaultValues{
   639  		Default:    "127.0.0.1,localhost,::1",
   640  		Controller: nil,
   641  		Regions: []config.RegionDefaultValue{
   642  			{
   643  				Name:  "dummy-region",
   644  				Value: "dummy-proxy",
   645  			}, {
   646  				Name:  "unused-region",
   647  				Value: "changed-proxy",
   648  			}}})
   649  }