Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
1217 lines (1133 sloc) 37.7 KB
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package config_test
import (
"fmt"
"strings"
stdtesting "testing"
"time"
"github.com/juju/loggo"
"github.com/juju/schema"
gitjujutesting "github.com/juju/testing"
jc "github.com/juju/testing/checkers"
"github.com/juju/utils/proxy"
"github.com/juju/utils/series"
"github.com/juju/version"
gc "gopkg.in/check.v1"
"gopkg.in/juju/charmrepo.v2-unstable"
"gopkg.in/juju/environschema.v1"
"github.com/juju/juju/cert"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/juju/osenv"
"github.com/juju/juju/testing"
)
func Test(t *stdtesting.T) {
gc.TestingT(t)
}
type ConfigSuite struct {
testing.FakeJujuXDGDataHomeSuite
home string
}
var _ = gc.Suite(&ConfigSuite{})
func (s *ConfigSuite) SetUpTest(c *gc.C) {
s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
// Make sure that the defaults are used, which
// is <root>=WARNING
loggo.DefaultContext().ResetLoggerLevels()
}
// sampleConfig holds a configuration with all required
// attributes set.
var sampleConfig = testing.Attrs{
"type": "my-type",
"name": "my-name",
"uuid": testing.ModelTag.Id(),
"authorized-keys": testing.FakeAuthKeys,
"firewall-mode": config.FwInstance,
"unknown": "my-unknown",
"ssl-hostname-verification": true,
"development": false,
"default-series": series.LatestLts(),
"disable-network-management": false,
"ignore-machine-addresses": false,
"automatically-retry-hooks": true,
"proxy-ssh": false,
"resource-tags": []string{},
}
type configTest struct {
about string
useDefaults config.Defaulting
attrs testing.Attrs
expected testing.Attrs
err string
}
var testResourceTags = []string{"a=b", "c=", "d=e"}
var testResourceTagsMap = map[string]string{
"a": "b", "c": "", "d": "e",
}
var minimalConfigAttrs = testing.Attrs{
"type": "my-type",
"name": "my-name",
"uuid": testing.ModelTag.Id(),
}
var modelNameErr = "%q is not a valid name: model names may only contain lowercase letters, digits and hyphens"
var configTests = []configTest{
{
about: "The minimum good configuration",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs,
}, {
about: "Agent Stream",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"image-metadata-url": "image-url",
"agent-stream": "released",
}),
}, {
about: "Metadata URLs",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"image-metadata-url": "image-url",
"agent-metadata-url": "agent-metadata-url-value",
}),
}, {
about: "Explicit series",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"default-series": "my-series",
}),
}, {
about: "Explicit logging",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"logging-config": "juju=INFO",
}),
}, {
about: "Explicit authorized-keys",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"authorized-keys": testing.FakeAuthKeys,
}),
}, {
about: "Specified agent version",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"agent-version": "1.2.3",
}),
}, {
about: "Specified development flag",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"development": true,
}),
}, {
about: "Invalid development flag",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"development": "invalid",
}),
err: `development: expected bool, got string\("invalid"\)`,
}, {
about: "Invalid disable-network-management flag",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"disable-network-management": "invalid",
}),
err: `disable-network-management: expected bool, got string\("invalid"\)`,
}, {
about: "disable-network-management off",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"disable-network-management": false,
}),
}, {
about: "disable-network-management on",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"disable-network-management": true,
}),
}, {
about: "Invalid ignore-machine-addresses flag",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"ignore-machine-addresses": "invalid",
}),
err: `ignore-machine-addresses: expected bool, got string\("invalid"\)`,
}, {
about: "ignore-machine-addresses off",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"ignore-machine-addresses": false,
}),
}, {
about: "ignore-machine-addresses on",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"ignore-machine-addresses": true,
}),
}, {
about: "set-numa-control-policy on",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"set-numa-control-policy": true,
}),
}, {
about: "set-numa-control-policy off",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"set-numa-control-policy": false,
}),
}, {
about: "Invalid agent version",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"agent-version": "2",
}),
err: `invalid agent version in model configuration: "2"`,
}, {
about: "Missing type",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Delete("type"),
err: "type: expected string, got nothing",
}, {
about: "Empty type",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"type": "",
}),
err: "empty type in model configuration",
}, {
about: "Missing name",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Delete("name"),
err: "name: expected string, got nothing",
}, {
about: "Bad name, no slash",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"name": "foo/bar",
}),
err: fmt.Sprintf(modelNameErr, "foo/bar"),
}, {
about: "Bad name, no backslash",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"name": "foo\\bar",
}),
// Double escape to keep the safe quote in the format string happy
err: fmt.Sprintf(modelNameErr, "foo\\\\bar"),
}, {
about: "Bad name, no space",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"name": "foo bar",
}),
err: fmt.Sprintf(modelNameErr, "foo bar"),
}, {
about: "Bad name, no capital",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"name": "fooBar",
}),
err: fmt.Sprintf(modelNameErr, "fooBar"),
}, {
about: "Empty name",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"name": "",
}),
err: "empty name in model configuration",
}, {
about: "Default firewall mode",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs,
}, {
about: "Empty firewall mode",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"firewall-mode": "",
}),
err: `firewall-mode: expected one of \[instance global none\], got ""`,
}, {
about: "Instance firewall mode",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"firewall-mode": config.FwInstance,
}),
}, {
about: "Global firewall mode",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"firewall-mode": config.FwGlobal,
}),
}, {
about: "None firewall mode",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"firewall-mode": config.FwNone,
}),
}, {
about: "Illegal firewall mode",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"firewall-mode": "illegal",
}),
err: `firewall-mode: expected one of \[instance global none\], got "illegal"`,
}, {
about: "ssl-hostname-verification off",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"ssl-hostname-verification": false,
}),
}, {
about: "ssl-hostname-verification incorrect",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"ssl-hostname-verification": "yes please",
}),
err: `ssl-hostname-verification: expected bool, got string\("yes please"\)`,
}, {
about: fmt.Sprintf(
"%s: %s",
"provisioner-harvest-mode",
config.HarvestAll.String(),
),
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"provisioner-harvest-mode": config.HarvestAll.String(),
}),
}, {
about: fmt.Sprintf(
"%s: %s",
"provisioner-harvest-mode",
config.HarvestDestroyed.String(),
),
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"provisioner-harvest-mode": config.HarvestDestroyed.String(),
}),
}, {
about: fmt.Sprintf(
"%s: %s",
"provisioner-harvest-mode",
config.HarvestUnknown.String(),
),
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"provisioner-harvest-mode": config.HarvestUnknown.String(),
}),
}, {
about: fmt.Sprintf(
"%s: %s",
"provisioner-harvest-mode",
config.HarvestNone.String(),
),
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"provisioner-harvest-mode": config.HarvestNone.String(),
}),
}, {
about: "provisioner-harvest-mode: incorrect",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"provisioner-harvest-mode": "yes please",
}),
err: `provisioner-harvest-mode: expected one of \[all none unknown destroyed], got "yes please"`,
}, {
about: "default image stream",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs,
}, {
about: "explicit image stream",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"image-stream": "daily",
}),
}, {
about: "explicit tools stream",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"agent-stream": "proposed",
}),
}, {
about: "Invalid logging configuration",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"logging-config": "foo=bar",
}),
err: `unknown severity level "bar"`,
}, {
about: "Sample configuration",
useDefaults: config.UseDefaults,
attrs: sampleConfig,
}, {
about: "No defaults: sample configuration",
useDefaults: config.NoDefaults,
attrs: sampleConfig,
}, {
about: "Config settings from juju actual installation",
useDefaults: config.NoDefaults,
attrs: map[string]interface{}{
"name": "sample",
"development": false,
"ssl-hostname-verification": true,
"authorized-keys": "ssh-rsa mykeys rog@rog-x220\n",
"region": "us-east-1",
"default-series": "precise",
"secret-key": "a-secret-key",
"access-key": "an-access-key",
"agent-version": "1.13.2",
"firewall-mode": "instance",
"disable-network-management": false,
"ignore-machine-addresses": false,
"automatically-retry-hooks": true,
"proxy-ssh": false,
"resource-tags": []string{},
"type": "ec2",
"uuid": testing.ModelTag.Id(),
},
}, {
about: "TestMode flag specified",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"test-mode": true,
}),
}, {
about: "valid uuid",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"uuid": "dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4",
}),
}, {
about: "invalid uuid 1",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"uuid": "dcfbdb4abca249adaa7cf011424e0fe4",
}),
err: `uuid: expected UUID, got string\("dcfbdb4abca249adaa7cf011424e0fe4"\)`,
}, {
about: "invalid uuid 2",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"uuid": "uuid",
}),
err: `uuid: expected UUID, got string\("uuid"\)`,
}, {
about: "blank uuid",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"uuid": "",
}),
err: `empty uuid in model configuration`,
},
{
about: "Explicit apt-mirror",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"apt-mirror": "http://my.archive.ubuntu.com",
}),
},
{
about: "Resource tags as space-separated string",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"resource-tags": strings.Join(testResourceTags, " "),
}),
},
{
about: "Resource tags as list of strings",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"resource-tags": testResourceTags,
}),
},
{
about: "Resource tags contains non-keyvalues",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"resource-tags": []string{"a"},
}),
err: `resource-tags: expected "key=value", got "a"`,
}, {
about: "Invalid syslog ca cert format",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"type": "my-type",
"name": "my-name",
"logforward-enabled": true,
"syslog-host": "localhost:1234",
"syslog-ca-cert": "abc",
"syslog-client-cert": testing.CACert,
"syslog-client-key": testing.CAKey,
}),
err: `invalid syslog forwarding config: validating TLS config: parsing CA certificate: no certificates found`,
}, {
about: "Invalid syslog ca cert",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"type": "my-type",
"name": "my-name",
"logforward-enabled": true,
"syslog-host": "localhost:1234",
"syslog-ca-cert": invalidCACert,
"syslog-client-cert": testing.CACert,
"syslog-client-key": testing.CAKey,
}),
err: `invalid syslog forwarding config: validating TLS config: parsing CA certificate: asn1: syntax error: data truncated`,
}, {
about: "invalid syslog cert",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"logforward-enabled": true,
"syslog-host": "10.0.0.1:12345",
"syslog-ca-cert": testing.CACert,
"syslog-client-cert": invalidCACert,
"syslog-client-key": testing.CAKey,
}),
err: `invalid syslog forwarding config: validating TLS config: parsing client key pair: asn1: syntax error: data truncated`,
}, {
about: "invalid syslog key",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"logforward-enabled": true,
"syslog-host": "10.0.0.1:12345",
"syslog-ca-cert": testing.CACert,
"syslog-client-cert": testing.CACert,
"syslog-client-key": invalidCAKey,
}),
err: `invalid syslog forwarding config: validating TLS config: parsing client key pair: (crypto/)?tls: failed to parse private key`,
}, {
about: "Mismatched syslog cert and key",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"logforward-enabled": true,
"syslog-host": "10.0.0.1:12345",
"syslog-ca-cert": testing.CACert,
"syslog-client-cert": testing.ServerCert,
"syslog-client-key": serverKey2,
}),
err: `invalid syslog forwarding config: validating TLS config: parsing client key pair: (crypto/)?tls: private key does not match public key`,
}, {
about: "net-bond-reconfigure-delay value",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
config.NetBondReconfigureDelayKey: 1234,
}),
}, {
about: "transmit-vendor-metrics asserted with default value",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"transmit-vendor-metrics": true,
}),
}, {
about: "transmit-vendor-metrics asserted false",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"transmit-vendor-metrics": false,
}),
}, {
about: "Valid syslog config values",
useDefaults: config.UseDefaults,
attrs: minimalConfigAttrs.Merge(testing.Attrs{
"type": "my-type",
"name": "my-name",
"logforward-enabled": true,
"syslog-host": "localhost:1234",
"syslog-ca-cert": testing.CACert,
"syslog-client-cert": testing.ServerCert,
"syslog-client-key": testing.ServerKey,
}),
},
}
type testFile struct {
name, data string
}
func (s *ConfigSuite) TestConfig(c *gc.C) {
files := []gitjujutesting.TestFile{
{".ssh/id_dsa.pub", "dsa"},
{".ssh/id_rsa.pub", "rsa\n"},
{".ssh/identity.pub", "identity"},
{".ssh/authorized_keys", "auth0\n# first\nauth1\n\n"},
{".ssh/authorized_keys2", "auth2\nauth3\n"},
}
s.FakeHomeSuite.Home.AddFiles(c, files...)
for i, test := range configTests {
c.Logf("test %d. %s", i, test.about)
test.check(c, s.FakeHomeSuite.Home)
}
}
func (test configTest) check(c *gc.C, home *gitjujutesting.FakeHome) {
cfg, err := config.New(test.useDefaults, test.attrs)
if test.err != "" {
c.Check(cfg, gc.IsNil)
c.Assert(err, gc.ErrorMatches, test.err)
return
}
c.Assert(err, jc.ErrorIsNil)
typ, _ := test.attrs["type"].(string)
// "null" has been deprecated in favour of "manual",
// and is automatically switched.
if typ == "null" {
typ = "manual"
}
name, _ := test.attrs["name"].(string)
c.Assert(cfg.Type(), gc.Equals, typ)
c.Assert(cfg.Name(), gc.Equals, name)
agentVersion, ok := cfg.AgentVersion()
if s := test.attrs["agent-version"]; s != nil {
c.Assert(ok, jc.IsTrue)
c.Assert(agentVersion, gc.Equals, version.MustParse(s.(string)))
} else {
c.Assert(ok, jc.IsFalse)
c.Assert(agentVersion, gc.Equals, version.Zero)
}
if expected, ok := test.attrs["uuid"]; ok {
c.Assert(cfg.UUID(), gc.Equals, expected)
}
dev, _ := test.attrs["development"].(bool)
c.Assert(cfg.Development(), gc.Equals, dev)
testmode, _ := test.attrs["test-mode"].(bool)
c.Assert(cfg.TestMode(), gc.Equals, testmode)
seriesAttr, _ := test.attrs["default-series"].(string)
defaultSeries, ok := cfg.DefaultSeries()
c.Assert(ok, jc.IsTrue)
if seriesAttr != "" {
c.Assert(defaultSeries, gc.Equals, seriesAttr)
} else {
c.Assert(defaultSeries, gc.Equals, series.LatestLts())
}
if m, _ := test.attrs["firewall-mode"].(string); m != "" {
c.Assert(cfg.FirewallMode(), gc.Equals, m)
}
keys, _ := test.attrs["authorized-keys"].(string)
c.Assert(cfg.AuthorizedKeys(), gc.Equals, keys)
lfCfg, hasLogCfg := cfg.LogFwdSyslog()
if v, ok := test.attrs["logforward-enabled"].(bool); ok {
c.Assert(hasLogCfg, jc.IsTrue)
c.Assert(lfCfg.Enabled, gc.Equals, v)
}
if v, ok := test.attrs["syslog-ca-cert"].(string); v != "" {
c.Assert(hasLogCfg, jc.IsTrue)
c.Assert(lfCfg.CACert, gc.Equals, v)
} else if ok {
c.Assert(hasLogCfg, jc.IsTrue)
c.Check(lfCfg.CACert, gc.Equals, "")
}
if v, ok := test.attrs["syslog-client-cert"].(string); v != "" {
c.Assert(hasLogCfg, jc.IsTrue)
c.Assert(lfCfg.ClientCert, gc.Equals, v)
} else if ok {
c.Assert(hasLogCfg, jc.IsTrue)
c.Check(lfCfg.ClientCert, gc.Equals, "")
}
if v, ok := test.attrs["syslog-client-key"].(string); v != "" {
c.Assert(hasLogCfg, jc.IsTrue)
c.Assert(lfCfg.ClientKey, gc.Equals, v)
} else if ok {
c.Assert(hasLogCfg, jc.IsTrue)
c.Check(lfCfg.ClientKey, gc.Equals, "")
}
if v, ok := test.attrs["ssl-hostname-verification"]; ok {
c.Assert(cfg.SSLHostnameVerification(), gc.Equals, v)
}
if v, ok := test.attrs["provisioner-harvest-mode"]; ok {
hvstMeth, err := config.ParseHarvestMode(v.(string))
c.Assert(err, jc.ErrorIsNil)
c.Assert(cfg.ProvisionerHarvestMode(), gc.Equals, hvstMeth)
} else {
c.Assert(cfg.ProvisionerHarvestMode(), gc.Equals, config.HarvestDestroyed)
}
if v, ok := test.attrs["image-stream"]; ok {
c.Assert(cfg.ImageStream(), gc.Equals, v)
} else {
c.Assert(cfg.ImageStream(), gc.Equals, "released")
}
url, urlPresent := cfg.ImageMetadataURL()
if v, _ := test.attrs["image-metadata-url"].(string); v != "" {
c.Assert(url, gc.Equals, v)
c.Assert(urlPresent, jc.IsTrue)
} else {
c.Assert(urlPresent, jc.IsFalse)
}
agentURL, urlPresent := cfg.AgentMetadataURL()
expectedToolsURLValue := test.attrs["agent-metadata-url"]
if urlPresent {
c.Assert(agentURL, gc.Equals, expectedToolsURLValue)
} else {
c.Assert(agentURL, gc.Equals, "")
}
// assertions for deprecated tools-stream attribute used with new agent-stream
agentStreamValue := cfg.AgentStream()
oldTstToolsStreamAttr, oldTstOk := test.attrs["tools-stream"]
expectedAgentStreamAttr := test.attrs["agent-stream"]
// When no agent-stream provided, look for tools-stream
if expectedAgentStreamAttr == nil {
if oldTstOk {
expectedAgentStreamAttr = oldTstToolsStreamAttr
} else {
// If it's still nil, then hard-coded default is used
expectedAgentStreamAttr = "released"
}
}
c.Assert(agentStreamValue, gc.Equals, expectedAgentStreamAttr)
resourceTags, cfgHasResourceTags := cfg.ResourceTags()
c.Assert(cfgHasResourceTags, jc.IsTrue)
if tags, ok := test.attrs["resource-tags"]; ok {
switch tags := tags.(type) {
case []string:
if len(tags) > 0 {
c.Assert(resourceTags, jc.DeepEquals, testResourceTagsMap)
}
case string:
if tags != "" {
c.Assert(resourceTags, jc.DeepEquals, testResourceTagsMap)
}
}
} else {
c.Assert(resourceTags, gc.HasLen, 0)
}
xmit := cfg.TransmitVendorMetrics()
expectedXmit, xmitAsserted := test.attrs["transmit-vendor-metrics"]
if xmitAsserted {
c.Check(xmit, gc.Equals, expectedXmit)
} else {
c.Check(xmit, jc.IsTrue)
}
if val, ok := test.attrs[config.NetBondReconfigureDelayKey].(int); ok {
c.Assert(cfg.NetBondReconfigureDelay(), gc.Equals, val)
}
}
func (test configTest) assertDuration(c *gc.C, name string, actual time.Duration, defaultInSeconds int) {
value, ok := test.attrs[name].(int)
if !ok || value == 0 {
c.Assert(actual, gc.Equals, time.Duration(defaultInSeconds)*time.Second)
} else {
c.Assert(actual, gc.Equals, time.Duration(value)*time.Second)
}
}
func (s *ConfigSuite) TestConfigAttrs(c *gc.C) {
// Normally this is handled by gitjujutesting.FakeHome
s.PatchEnvironment(osenv.JujuLoggingConfigEnvKey, "")
attrs := map[string]interface{}{
"type": "my-type",
"name": "my-name",
"uuid": "90168e4c-2f10-4e9c-83c2-1fb55a58e5a9",
"authorized-keys": testing.FakeAuthKeys,
"firewall-mode": config.FwInstance,
"unknown": "my-unknown",
"ssl-hostname-verification": true,
"default-series": series.LatestLts(),
"disable-network-management": false,
"ignore-machine-addresses": false,
"automatically-retry-hooks": true,
"proxy-ssh": false,
"development": false,
"test-mode": false,
}
cfg, err := config.New(config.NoDefaults, attrs)
c.Assert(err, jc.ErrorIsNil)
// These attributes are added if not set.
attrs["logging-config"] = "<root>=WARNING;unit=DEBUG"
// Default firewall mode is instance
attrs["firewall-mode"] = string(config.FwInstance)
c.Assert(cfg.AllAttrs(), jc.DeepEquals, attrs)
c.Assert(cfg.UnknownAttrs(), jc.DeepEquals, map[string]interface{}{"unknown": "my-unknown"})
// Verify that default provisioner-harvest-mode is good.
c.Assert(cfg.ProvisionerHarvestMode(), gc.Equals, config.HarvestDestroyed)
newcfg, err := cfg.Apply(map[string]interface{}{
"name": "new-name",
"uuid": "6216dfc3-6e82-408f-9f74-8565e63e6158",
"new-unknown": "my-new-unknown",
})
c.Assert(err, jc.ErrorIsNil)
attrs["name"] = "new-name"
attrs["uuid"] = "6216dfc3-6e82-408f-9f74-8565e63e6158"
attrs["new-unknown"] = "my-new-unknown"
c.Assert(newcfg.AllAttrs(), jc.DeepEquals, attrs)
}
type validationTest struct {
about string
new testing.Attrs
old testing.Attrs
err string
}
var validationTests = []validationTest{{
about: "Can't change the type",
new: testing.Attrs{"type": "new-type"},
err: `cannot change type from "my-type" to "new-type"`,
}, {
about: "Can't change the name",
new: testing.Attrs{"name": "new-name"},
err: `cannot change name from "my-name" to "new-name"`,
}, {
about: "Can set agent version",
new: testing.Attrs{"agent-version": "1.9.13"},
}, {
about: "Can change agent version",
old: testing.Attrs{"agent-version": "1.9.13"},
new: testing.Attrs{"agent-version": "1.9.27"},
}, {
about: "Can't clear agent version",
old: testing.Attrs{"agent-version": "1.9.27"},
err: `cannot clear agent-version`,
}, {
about: "Can't change the firewall-mode (global->instance)",
old: testing.Attrs{"firewall-mode": config.FwGlobal},
new: testing.Attrs{"firewall-mode": config.FwInstance},
err: `cannot change firewall-mode from "global" to "instance"`,
}, {
about: "Can't change the firewall-mode (global->none)",
old: testing.Attrs{"firewall-mode": config.FwGlobal},
new: testing.Attrs{"firewall-mode": config.FwNone},
err: `cannot change firewall-mode from "global" to "none"`,
}, {
about: "Cannot change uuid",
old: testing.Attrs{"uuid": "90168e4c-2f10-4e9c-83c2-1fb55a58e5a9"},
new: testing.Attrs{"uuid": "dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4"},
err: "cannot change uuid from \"90168e4c-2f10-4e9c-83c2-1fb55a58e5a9\" to \"dcfbdb4a-bca2-49ad-aa7c-f011424e0fe4\"",
}}
func (s *ConfigSuite) TestValidateChange(c *gc.C) {
files := []gitjujutesting.TestFile{
{".ssh/identity.pub", "identity"},
}
s.FakeHomeSuite.Home.AddFiles(c, files...)
for i, test := range validationTests {
c.Logf("test %d: %s", i, test.about)
newConfig := newTestConfig(c, test.new)
oldConfig := newTestConfig(c, test.old)
err := config.Validate(newConfig, oldConfig)
if test.err == "" {
c.Check(err, jc.ErrorIsNil)
} else {
c.Check(err, gc.ErrorMatches, test.err)
}
}
}
func (s *ConfigSuite) addJujuFiles(c *gc.C) {
s.FakeHomeSuite.Home.AddFiles(c, []gitjujutesting.TestFile{
{".ssh/id_rsa.pub", "rsa\n"},
}...)
}
func (s *ConfigSuite) TestValidateUnknownAttrs(c *gc.C) {
s.addJujuFiles(c)
cfg, err := config.New(config.UseDefaults, map[string]interface{}{
"name": "myenv",
"type": "other",
"uuid": testing.ModelTag.Id(),
"extra-info": "official extra user data",
"known": "this",
"unknown": "that",
})
c.Assert(err, jc.ErrorIsNil)
// No fields: all attrs passed through.
attrs, err := cfg.ValidateUnknownAttrs(nil, nil)
c.Assert(err, jc.ErrorIsNil)
c.Assert(attrs, gc.DeepEquals, map[string]interface{}{
"known": "this",
"unknown": "that",
})
// Valid field: that and other attrs passed through.
fields := schema.Fields{"known": schema.String()}
attrs, err = cfg.ValidateUnknownAttrs(fields, nil)
c.Assert(err, jc.ErrorIsNil)
c.Assert(attrs, gc.DeepEquals, map[string]interface{}{
"known": "this",
"unknown": "that",
})
// Default field: inserted.
fields["default"] = schema.String()
defaults := schema.Defaults{"default": "the other"}
attrs, err = cfg.ValidateUnknownAttrs(fields, defaults)
c.Assert(err, jc.ErrorIsNil)
c.Assert(attrs, gc.DeepEquals, map[string]interface{}{
"known": "this",
"unknown": "that",
"default": "the other",
})
// Invalid field: failure.
fields["known"] = schema.Int()
_, err = cfg.ValidateUnknownAttrs(fields, defaults)
c.Assert(err, gc.ErrorMatches, `known: expected int, got string\("this"\)`)
}
type testAttr struct {
message string
aKey string
aValue string
checker gc.Checker
}
var emptyAttributeTests = []testAttr{
{
message: "Warning message about unknown attribute (%v) is expected because attribute value exists",
aKey: "unknown",
aValue: "unknown value",
checker: gc.Matches,
}, {
message: "Warning message about unknown attribute (%v) is unexpected because attribute value is empty",
aKey: "unknown-empty",
aValue: "",
checker: gc.Not(gc.Matches),
},
}
func (s *ConfigSuite) TestValidateUnknownEmptyAttr(c *gc.C) {
s.addJujuFiles(c)
cfg, err := config.New(config.UseDefaults, map[string]interface{}{
"name": "myenv",
"type": "other",
"uuid": testing.ModelTag.Id(),
})
c.Assert(err, jc.ErrorIsNil)
warningTxt := `.* unknown config field %q.*`
for i, test := range emptyAttributeTests {
c.Logf("test %d: %v\n", i, fmt.Sprintf(test.message, test.aKey))
testCfg, err := cfg.Apply(map[string]interface{}{test.aKey: test.aValue})
c.Assert(err, jc.ErrorIsNil)
attrs, err := testCfg.ValidateUnknownAttrs(nil, nil)
c.Assert(err, jc.ErrorIsNil)
// all attrs passed through
c.Assert(attrs, gc.DeepEquals, map[string]interface{}{test.aKey: test.aValue})
expectedWarning := fmt.Sprintf(warningTxt, test.aKey)
logOutputText := strings.Replace(c.GetTestLog(), "\n", "", -1)
// warning displayed or not based on test expectation
c.Assert(logOutputText, test.checker, expectedWarning, gc.Commentf(test.message, test.aKey))
}
}
func newTestConfig(c *gc.C, explicit testing.Attrs) *config.Config {
final := testing.Attrs{
"type": "my-type", "name": "my-name",
"uuid": testing.ModelTag.Id(),
}
for key, value := range explicit {
final[key] = value
}
result, err := config.New(config.UseDefaults, final)
c.Assert(err, jc.ErrorIsNil)
return result
}
func (s *ConfigSuite) TestLoggingConfig(c *gc.C) {
s.addJujuFiles(c)
config := newTestConfig(c, testing.Attrs{
"logging-config": "<root>=WARNING;juju=DEBUG"})
c.Assert(config.LoggingConfig(), gc.Equals, "<root>=WARNING;juju=DEBUG;unit=DEBUG")
}
func (s *ConfigSuite) TestLoggingConfigWithUnit(c *gc.C) {
s.addJujuFiles(c)
config := newTestConfig(c, testing.Attrs{
"logging-config": "<root>=WARNING;unit=INFO"})
c.Assert(config.LoggingConfig(), gc.Equals, "<root>=WARNING;unit=INFO")
}
func (s *ConfigSuite) TestLoggingConfigFromEnvironment(c *gc.C) {
s.addJujuFiles(c)
s.PatchEnvironment(osenv.JujuLoggingConfigEnvKey, "<root>=INFO")
config := newTestConfig(c, nil)
c.Assert(config.LoggingConfig(), gc.Equals, "<root>=INFO;unit=DEBUG")
}
func (s *ConfigSuite) TestAutoHookRetryDefault(c *gc.C) {
config := newTestConfig(c, testing.Attrs{})
c.Assert(config.AutomaticallyRetryHooks(), gc.Equals, true)
}
func (s *ConfigSuite) TestAutoHookRetryFalseEnv(c *gc.C) {
config := newTestConfig(c, testing.Attrs{
"automatically-retry-hooks": "false"})
c.Assert(config.AutomaticallyRetryHooks(), gc.Equals, false)
}
func (s *ConfigSuite) TestAutoHookRetryTrueEnv(c *gc.C) {
config := newTestConfig(c, testing.Attrs{
"automatically-retry-hooks": "true"})
c.Assert(config.AutomaticallyRetryHooks(), gc.Equals, true)
}
func (s *ConfigSuite) TestProxyValuesWithFallback(c *gc.C) {
s.addJujuFiles(c)
config := newTestConfig(c, testing.Attrs{
"http-proxy": "http://user@10.0.0.1",
"https-proxy": "https://user@10.0.0.1",
"ftp-proxy": "ftp://user@10.0.0.1",
"no-proxy": "localhost,10.0.3.1",
})
c.Assert(config.HTTPProxy(), gc.Equals, "http://user@10.0.0.1")
c.Assert(config.AptHTTPProxy(), gc.Equals, "http://user@10.0.0.1")
c.Assert(config.HTTPSProxy(), gc.Equals, "https://user@10.0.0.1")
c.Assert(config.AptHTTPSProxy(), gc.Equals, "https://user@10.0.0.1")
c.Assert(config.FTPProxy(), gc.Equals, "ftp://user@10.0.0.1")
c.Assert(config.AptFTPProxy(), gc.Equals, "ftp://user@10.0.0.1")
c.Assert(config.NoProxy(), gc.Equals, "localhost,10.0.3.1")
}
func (s *ConfigSuite) TestProxyValuesWithFallbackNoScheme(c *gc.C) {
s.addJujuFiles(c)
config := newTestConfig(c, testing.Attrs{
"http-proxy": "user@10.0.0.1",
"https-proxy": "user@10.0.0.1",
"ftp-proxy": "user@10.0.0.1",
"no-proxy": "localhost,10.0.3.1",
})
c.Assert(config.HTTPProxy(), gc.Equals, "user@10.0.0.1")
c.Assert(config.AptHTTPProxy(), gc.Equals, "http://user@10.0.0.1")
c.Assert(config.HTTPSProxy(), gc.Equals, "user@10.0.0.1")
c.Assert(config.AptHTTPSProxy(), gc.Equals, "https://user@10.0.0.1")
c.Assert(config.FTPProxy(), gc.Equals, "user@10.0.0.1")
c.Assert(config.AptFTPProxy(), gc.Equals, "ftp://user@10.0.0.1")
c.Assert(config.NoProxy(), gc.Equals, "localhost,10.0.3.1")
}
func (s *ConfigSuite) TestProxyValues(c *gc.C) {
s.addJujuFiles(c)
config := newTestConfig(c, testing.Attrs{
"http-proxy": "http://user@10.0.0.1",
"https-proxy": "https://user@10.0.0.1",
"ftp-proxy": "ftp://user@10.0.0.1",
"apt-http-proxy": "http://user@10.0.0.2",
"apt-https-proxy": "https://user@10.0.0.2",
"apt-ftp-proxy": "ftp://user@10.0.0.2",
})
c.Assert(config.HTTPProxy(), gc.Equals, "http://user@10.0.0.1")
c.Assert(config.AptHTTPProxy(), gc.Equals, "http://user@10.0.0.2")
c.Assert(config.HTTPSProxy(), gc.Equals, "https://user@10.0.0.1")
c.Assert(config.AptHTTPSProxy(), gc.Equals, "https://user@10.0.0.2")
c.Assert(config.FTPProxy(), gc.Equals, "ftp://user@10.0.0.1")
c.Assert(config.AptFTPProxy(), gc.Equals, "ftp://user@10.0.0.2")
}
func (s *ConfigSuite) TestProxyValuesNotSet(c *gc.C) {
s.addJujuFiles(c)
config := newTestConfig(c, testing.Attrs{})
c.Assert(config.HTTPProxy(), gc.Equals, "")
c.Assert(config.AptHTTPProxy(), gc.Equals, "")
c.Assert(config.HTTPSProxy(), gc.Equals, "")
c.Assert(config.AptHTTPSProxy(), gc.Equals, "")
c.Assert(config.FTPProxy(), gc.Equals, "")
c.Assert(config.AptFTPProxy(), gc.Equals, "")
c.Assert(config.NoProxy(), gc.Equals, "127.0.0.1,localhost,::1")
}
func (s *ConfigSuite) TestProxyConfigMap(c *gc.C) {
s.addJujuFiles(c)
cfg := newTestConfig(c, testing.Attrs{})
proxySettings := proxy.Settings{
Http: "http proxy",
Https: "https proxy",
Ftp: "ftp proxy",
NoProxy: "no proxy",
}
expectedProxySettings := proxy.Settings{
Http: "http://http proxy",
Https: "https://https proxy",
Ftp: "ftp://ftp proxy",
NoProxy: "",
}
cfg, err := cfg.Apply(config.ProxyConfigMap(proxySettings))
c.Assert(err, jc.ErrorIsNil)
c.Assert(cfg.ProxySettings(), gc.DeepEquals, proxySettings)
// Apt proxy settings always include the scheme. NoProxy is empty.
c.Assert(cfg.AptProxySettings(), gc.DeepEquals, expectedProxySettings)
}
func (s *ConfigSuite) TestAptProxyConfigMap(c *gc.C) {
s.addJujuFiles(c)
cfg := newTestConfig(c, testing.Attrs{})
proxySettings := proxy.Settings{
Http: "http://httpproxy",
Https: "https://httpsproxy",
Ftp: "ftp://ftpproxy",
}
cfg, err := cfg.Apply(config.AptProxyConfigMap(proxySettings))
c.Assert(err, jc.ErrorIsNil)
// The default proxy settings should still be empty.
c.Assert(cfg.ProxySettings(), gc.DeepEquals, proxy.Settings{NoProxy: "127.0.0.1,localhost,::1"})
c.Assert(cfg.AptProxySettings(), gc.DeepEquals, proxySettings)
}
func (s *ConfigSuite) TestSchemaNoExtra(c *gc.C) {
schema, err := config.Schema(nil)
c.Assert(err, gc.IsNil)
orig := make(environschema.Fields)
for name, field := range config.ConfigSchema {
orig[name] = field
}
c.Assert(schema, jc.DeepEquals, orig)
// Check that we actually returned a copy, not the original.
schema["foo"] = environschema.Attr{}
_, ok := orig["foo"]
c.Assert(ok, jc.IsFalse)
}
func (s *ConfigSuite) TestSchemaWithExtraFields(c *gc.C) {
extraField := environschema.Attr{
Description: "fooish",
Type: environschema.Tstring,
}
schema, err := config.Schema(environschema.Fields{
"foo": extraField,
})
c.Assert(err, gc.IsNil)
c.Assert(schema["foo"], gc.DeepEquals, extraField)
delete(schema, "foo")
orig := make(environschema.Fields)
for name, field := range config.ConfigSchema {
orig[name] = field
}
c.Assert(schema, jc.DeepEquals, orig)
}
func (s *ConfigSuite) TestSchemaWithExtraOverlap(c *gc.C) {
schema, err := config.Schema(environschema.Fields{
"type": environschema.Attr{
Description: "duplicate",
Type: environschema.Tstring,
},
})
c.Assert(err, gc.ErrorMatches, `config field "type" clashes with global config`)
c.Assert(schema, gc.IsNil)
}
func (s *ConfigSuite) TestCoerceForStorage(c *gc.C) {
cfg := newTestConfig(c, testing.Attrs{
"resource-tags": "a=b c=d"})
tags, ok := cfg.ResourceTags()
c.Assert(ok, jc.IsTrue)
expectedTags := map[string]string{"a": "b", "c": "d"}
c.Assert(tags, gc.DeepEquals, expectedTags)
tagsStr := config.CoerceForStorage(cfg.AllAttrs())["resource-tags"].(string)
tagItems := strings.Split(tagsStr, " ")
tagsMap := make(map[string]string)
for _, kv := range tagItems {
parts := strings.Split(kv, "=")
tagsMap[parts[0]] = parts[1]
}
c.Assert(tagsMap, gc.DeepEquals, expectedTags)
}
var specializeCharmRepoTests = []struct {
about string
testMode bool
repo charmrepo.Interface
}{{
about: "test mode disabled, charm store",
repo: &specializedCharmRepo{},
}, {
about: "test mode enabled, charm store",
testMode: true,
repo: &specializedCharmRepo{},
}}
func (s *ConfigSuite) TestSpecializeCharmRepo(c *gc.C) {
for i, test := range specializeCharmRepoTests {
c.Logf("test %d: %s", i, test.about)
cfg := newTestConfig(c, testing.Attrs{"test-mode": test.testMode})
repo := config.SpecializeCharmRepo(test.repo, cfg)
store := repo.(*specializedCharmRepo)
c.Assert(store.testMode, gc.Equals, test.testMode)
}
}
type specializedCharmRepo struct {
*charmrepo.CharmStore
testMode bool
}
func (s *specializedCharmRepo) WithTestMode() charmrepo.Interface {
s.testMode = true
return s
}
var serverKey2 = func() string {
_, key, err := cert.NewDefaultServer(testing.CACert, testing.CAKey, nil)
if err != nil {
panic(err)
}
return string(key)
}()
var invalidCAKey = `
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJAZabKgKInuOxj5vDWLwHHQtK3/45KB+32D15w94Nt83BmuGxo90lw
-----END RSA PRIVATE KEY-----
`[1:]
var invalidCACert = `
-----BEGIN CERTIFICATE-----
MIIBOgIBAAJAZabKgKInuOxj5vDWLwHHQtK3/45KB+32D15w94Nt83BmuGxo90lw
-----END CERTIFICATE-----
`[1:]