github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/openstack/config_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/juju/loggo"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "launchpad.net/gocheck"
    13  
    14  	"github.com/juju/juju/environs"
    15  	"github.com/juju/juju/environs/config"
    16  	"github.com/juju/juju/testing"
    17  )
    18  
    19  type ConfigSuite struct {
    20  	testing.BaseSuite
    21  	savedVars map[string]string
    22  }
    23  
    24  // Ensure any environment variables a user may have set locally are reset.
    25  var envVars = map[string]string{
    26  	"AWS_SECRET_ACCESS_KEY": "",
    27  	"EC2_SECRET_KEYS":       "",
    28  	"NOVA_API_KEY":          "",
    29  	"NOVA_PASSWORD":         "",
    30  	"NOVA_PROJECT_ID":       "",
    31  	"NOVA_REGION":           "",
    32  	"NOVA_USERNAME":         "",
    33  	"OS_ACCESS_KEY":         "",
    34  	"OS_AUTH_URL":           "",
    35  	"OS_PASSWORD":           "",
    36  	"OS_REGION_NAME":        "",
    37  	"OS_SECRET_KEY":         "",
    38  	"OS_TENANT_NAME":        "",
    39  	"OS_USERNAME":           "",
    40  }
    41  
    42  var _ = gc.Suite(&ConfigSuite{})
    43  
    44  // configTest specifies a config parsing test, checking that env when
    45  // parsed as the openstack section of a config file matches
    46  // baseConfigResult when mutated by the mutate function, or that the
    47  // parse matches the given error.
    48  type configTest struct {
    49  	summary                 string
    50  	config                  map[string]interface{}
    51  	change                  map[string]interface{}
    52  	expect                  map[string]interface{}
    53  	envVars                 map[string]string
    54  	region                  string
    55  	controlBucket           string
    56  	useFloatingIP           bool
    57  	useDefaultSecurityGroup bool
    58  	network                 string
    59  	username                string
    60  	password                string
    61  	tenantName              string
    62  	authMode                string
    63  	authURL                 string
    64  	accessKey               string
    65  	secretKey               string
    66  	firewallMode            string
    67  	err                     string
    68  	sslHostnameVerification bool
    69  	sslHostnameSet          bool
    70  }
    71  
    72  type attrs map[string]interface{}
    73  
    74  func restoreEnvVars(envVars map[string]string) {
    75  	for k, v := range envVars {
    76  		os.Setenv(k, v)
    77  	}
    78  }
    79  
    80  func (t configTest) check(c *gc.C) {
    81  	attrs := testing.FakeConfig().Merge(testing.Attrs{
    82  		"type":           "openstack",
    83  		"control-bucket": "x",
    84  	}).Merge(t.config)
    85  
    86  	cfg, err := config.New(config.NoDefaults, attrs)
    87  	c.Assert(err, gc.IsNil)
    88  
    89  	// Set environment variables if any.
    90  	savedVars := make(map[string]string)
    91  	if t.envVars != nil {
    92  		for k, v := range t.envVars {
    93  			savedVars[k] = os.Getenv(k)
    94  			os.Setenv(k, v)
    95  		}
    96  	}
    97  	defer restoreEnvVars(savedVars)
    98  
    99  	e, err := environs.New(cfg)
   100  	if t.change != nil {
   101  		c.Assert(err, gc.IsNil)
   102  
   103  		// Testing a change in configuration.
   104  		var old, changed, valid *config.Config
   105  		osenv := e.(*environ)
   106  		old = osenv.ecfg().Config
   107  		changed, err = old.Apply(t.change)
   108  		c.Assert(err, gc.IsNil)
   109  
   110  		// Keep err for validation below.
   111  		valid, err = providerInstance.Validate(changed, old)
   112  		if err == nil {
   113  			err = osenv.SetConfig(valid)
   114  		}
   115  	}
   116  	if t.err != "" {
   117  		c.Check(err, gc.ErrorMatches, t.err)
   118  		return
   119  	}
   120  	c.Assert(err, gc.IsNil)
   121  
   122  	ecfg := e.(*environ).ecfg()
   123  	c.Assert(ecfg.Name(), gc.Equals, "testenv")
   124  	c.Assert(ecfg.controlBucket(), gc.Equals, "x")
   125  	if t.region != "" {
   126  		c.Assert(ecfg.region(), gc.Equals, t.region)
   127  	}
   128  	if t.authMode != "" {
   129  		c.Assert(ecfg.authMode(), gc.Equals, t.authMode)
   130  	}
   131  	if t.accessKey != "" {
   132  		c.Assert(ecfg.accessKey(), gc.Equals, t.accessKey)
   133  	}
   134  	if t.secretKey != "" {
   135  		c.Assert(ecfg.secretKey(), gc.Equals, t.secretKey)
   136  	}
   137  	if t.username != "" {
   138  		c.Assert(ecfg.username(), gc.Equals, t.username)
   139  		c.Assert(ecfg.password(), gc.Equals, t.password)
   140  		c.Assert(ecfg.tenantName(), gc.Equals, t.tenantName)
   141  		c.Assert(ecfg.authURL(), gc.Equals, t.authURL)
   142  		expected := map[string]string{
   143  			"username":    t.username,
   144  			"password":    t.password,
   145  			"tenant-name": t.tenantName,
   146  		}
   147  		c.Assert(err, gc.IsNil)
   148  		actual, err := e.Provider().SecretAttrs(ecfg.Config)
   149  		c.Assert(err, gc.IsNil)
   150  		c.Assert(expected, gc.DeepEquals, actual)
   151  	}
   152  	if t.firewallMode != "" {
   153  		c.Assert(ecfg.FirewallMode(), gc.Equals, t.firewallMode)
   154  	}
   155  	c.Assert(ecfg.useFloatingIP(), gc.Equals, t.useFloatingIP)
   156  	c.Assert(ecfg.useDefaultSecurityGroup(), gc.Equals, t.useDefaultSecurityGroup)
   157  	c.Assert(ecfg.network(), gc.Equals, t.network)
   158  	// Default should be true
   159  	expectedHostnameVerification := true
   160  	if t.sslHostnameSet {
   161  		expectedHostnameVerification = t.sslHostnameVerification
   162  	}
   163  	c.Assert(ecfg.SSLHostnameVerification(), gc.Equals, expectedHostnameVerification)
   164  	for name, expect := range t.expect {
   165  		actual, found := ecfg.UnknownAttrs()[name]
   166  		c.Check(found, gc.Equals, true)
   167  		c.Check(actual, gc.Equals, expect)
   168  	}
   169  }
   170  
   171  func (s *ConfigSuite) SetUpTest(c *gc.C) {
   172  	s.BaseSuite.SetUpTest(c)
   173  	s.savedVars = make(map[string]string)
   174  	for v, val := range envVars {
   175  		s.savedVars[v] = os.Getenv(v)
   176  		os.Setenv(v, val)
   177  	}
   178  }
   179  
   180  func (s *ConfigSuite) TearDownTest(c *gc.C) {
   181  	for k, v := range s.savedVars {
   182  		os.Setenv(k, v)
   183  	}
   184  	s.BaseSuite.TearDownTest(c)
   185  }
   186  
   187  var configTests = []configTest{
   188  	{
   189  		summary: "setting region",
   190  		config: attrs{
   191  			"region": "testreg",
   192  		},
   193  		region: "testreg",
   194  	}, {
   195  		summary: "setting region (2)",
   196  		config: attrs{
   197  			"region": "configtest",
   198  		},
   199  		region: "configtest",
   200  	}, {
   201  		summary: "changing region",
   202  		config: attrs{
   203  			"region": "configtest",
   204  		},
   205  		change: attrs{
   206  			"region": "somereg",
   207  		},
   208  		err: `cannot change region from "configtest" to "somereg"`,
   209  	}, {
   210  		summary: "invalid region",
   211  		config: attrs{
   212  			"region": 666,
   213  		},
   214  		err: `.*expected string, got int\(666\)`,
   215  	}, {
   216  		summary: "missing region in environment",
   217  		envVars: map[string]string{
   218  			"OS_REGION_NAME": "",
   219  			"NOVA_REGION":    "",
   220  		},
   221  		err: "required environment variable not set for credentials attribute: Region",
   222  	}, {
   223  		summary: "invalid username",
   224  		config: attrs{
   225  			"username": 666,
   226  		},
   227  		err: `.*expected string, got int\(666\)`,
   228  	}, {
   229  		summary: "missing username in environment",
   230  		err:     "required environment variable not set for credentials attribute: User",
   231  		envVars: map[string]string{
   232  			"OS_USERNAME":   "",
   233  			"NOVA_USERNAME": "",
   234  		},
   235  	}, {
   236  		summary: "invalid password",
   237  		config: attrs{
   238  			"password": 666,
   239  		},
   240  		err: `.*expected string, got int\(666\)`,
   241  	}, {
   242  		summary: "missing password in environment",
   243  		err:     "required environment variable not set for credentials attribute: Secrets",
   244  		envVars: map[string]string{
   245  			"OS_PASSWORD":   "",
   246  			"NOVA_PASSWORD": "",
   247  		},
   248  	}, {
   249  		summary: "invalid tenant-name",
   250  		config: attrs{
   251  			"tenant-name": 666,
   252  		},
   253  		err: `.*expected string, got int\(666\)`,
   254  	}, {
   255  		summary: "missing tenant in environment",
   256  		err:     "required environment variable not set for credentials attribute: TenantName",
   257  		envVars: map[string]string{
   258  			"OS_TENANT_NAME":  "",
   259  			"NOVA_PROJECT_ID": "",
   260  		},
   261  	}, {
   262  		summary: "invalid auth-url type",
   263  		config: attrs{
   264  			"auth-url": 666,
   265  		},
   266  		err: `.*expected string, got int\(666\)`,
   267  	}, {
   268  		summary: "missing auth-url in environment",
   269  		err:     "required environment variable not set for credentials attribute: URL",
   270  		envVars: map[string]string{
   271  			"OS_AUTH_URL": "",
   272  		},
   273  	}, {
   274  		summary: "invalid authorization mode",
   275  		config: attrs{
   276  			"auth-mode": "invalid-mode",
   277  		},
   278  		err: ".*invalid authorization mode.*",
   279  	}, {
   280  		summary: "keypair authorization mode",
   281  		config: attrs{
   282  			"auth-mode":  "keypair",
   283  			"access-key": "MyAccessKey",
   284  			"secret-key": "MySecretKey",
   285  		},
   286  		authMode:  "keypair",
   287  		accessKey: "MyAccessKey",
   288  		secretKey: "MySecretKey",
   289  	}, {
   290  		summary: "keypair authorization mode without access key",
   291  		config: attrs{
   292  			"auth-mode":  "keypair",
   293  			"secret-key": "MySecretKey",
   294  		},
   295  		envVars: map[string]string{
   296  			"OS_USERNAME": "",
   297  		},
   298  		err: "required environment variable not set for credentials attribute: User",
   299  	}, {
   300  		summary: "keypair authorization mode without secret key",
   301  		config: attrs{
   302  			"auth-mode":  "keypair",
   303  			"access-key": "MyAccessKey",
   304  		},
   305  		envVars: map[string]string{
   306  			"OS_PASSWORD": "",
   307  		},
   308  		err: "required environment variable not set for credentials attribute: Secrets",
   309  	}, {
   310  		summary: "invalid auth-url format",
   311  		config: attrs{
   312  			"auth-url": "invalid",
   313  		},
   314  		err: `invalid auth-url value "invalid"`,
   315  	}, {
   316  		summary: "invalid control-bucket",
   317  		config: attrs{
   318  			"control-bucket": 666,
   319  		},
   320  		err: `.*expected string, got int\(666\)`,
   321  	}, {
   322  		summary: "changing control-bucket",
   323  		change: attrs{
   324  			"control-bucket": "new-x",
   325  		},
   326  		err: `cannot change control-bucket from "x" to "new-x"`,
   327  	}, {
   328  		summary: "valid auth args",
   329  		config: attrs{
   330  			"username":    "jujuer",
   331  			"password":    "open sesame",
   332  			"tenant-name": "juju tenant",
   333  			"auth-mode":   "legacy",
   334  			"auth-url":    "http://some/url",
   335  		},
   336  		username:   "jujuer",
   337  		password:   "open sesame",
   338  		tenantName: "juju tenant",
   339  		authURL:    "http://some/url",
   340  		authMode:   string(AuthLegacy),
   341  	}, {
   342  		summary: "valid auth args in environment",
   343  		envVars: map[string]string{
   344  			"OS_USERNAME":    "jujuer",
   345  			"OS_PASSWORD":    "open sesame",
   346  			"OS_AUTH_URL":    "http://some/url",
   347  			"OS_TENANT_NAME": "juju tenant",
   348  			"OS_REGION_NAME": "region",
   349  		},
   350  		username:   "jujuer",
   351  		password:   "open sesame",
   352  		tenantName: "juju tenant",
   353  		authURL:    "http://some/url",
   354  		region:     "region",
   355  	}, {
   356  		summary:  "default auth mode based on environment",
   357  		authMode: string(AuthUserPass),
   358  	}, {
   359  		summary: "default use floating ip",
   360  		// Do not use floating IP's by default.
   361  		useFloatingIP: false,
   362  	}, {
   363  		summary: "use floating ip",
   364  		config: attrs{
   365  			"use-floating-ip": true,
   366  		},
   367  		useFloatingIP: true,
   368  	}, {
   369  		summary: "default use default security group",
   370  		// Do not use default security group by default.
   371  		useDefaultSecurityGroup: false,
   372  	}, {
   373  		summary: "use default security group",
   374  		config: attrs{
   375  			"use-default-secgroup": true,
   376  		},
   377  		useDefaultSecurityGroup: true,
   378  	}, {
   379  		summary: "admin-secret given",
   380  		config: attrs{
   381  			"admin-secret": "Futumpsh",
   382  		},
   383  	}, {
   384  		summary:      "default firewall-mode",
   385  		config:       attrs{},
   386  		firewallMode: config.FwInstance,
   387  	}, {
   388  		summary: "instance firewall-mode",
   389  		config: attrs{
   390  			"firewall-mode": "instance",
   391  		},
   392  		firewallMode: config.FwInstance,
   393  	}, {
   394  		summary: "global firewall-mode",
   395  		config: attrs{
   396  			"firewall-mode": "global",
   397  		},
   398  		firewallMode: config.FwGlobal,
   399  	}, {
   400  		config: attrs{
   401  			"future": "hammerstein",
   402  		},
   403  		expect: attrs{
   404  			"future": "hammerstein",
   405  		},
   406  	}, {
   407  		change: attrs{
   408  			"future": "hammerstein",
   409  		},
   410  		expect: attrs{
   411  			"future": "hammerstein",
   412  		},
   413  	}, {
   414  		change: attrs{
   415  			"ssl-hostname-verification": false,
   416  		},
   417  		sslHostnameVerification: false,
   418  		sslHostnameSet:          true,
   419  	}, {
   420  		change: attrs{
   421  			"ssl-hostname-verification": true,
   422  		},
   423  		sslHostnameVerification: true,
   424  		sslHostnameSet:          true,
   425  	}, {
   426  		summary: "default network",
   427  		network: "",
   428  	}, {
   429  		summary: "network",
   430  		config: attrs{
   431  			"network": "a-network-label",
   432  		},
   433  		network: "a-network-label",
   434  	},
   435  }
   436  
   437  func (s *ConfigSuite) TestConfig(c *gc.C) {
   438  	s.setupEnvCredentials()
   439  	for i, t := range configTests {
   440  		c.Logf("test %d: %s (%v)", i, t.summary, t.config)
   441  		t.check(c)
   442  	}
   443  }
   444  
   445  func (s *ConfigSuite) TestDeprecatedAttributesRemoved(c *gc.C) {
   446  	s.setupEnvCredentials()
   447  	attrs := testing.FakeConfig().Merge(testing.Attrs{
   448  		"type":                  "openstack",
   449  		"control-bucket":        "x",
   450  		"default-image-id":      "id-1234",
   451  		"default-instance-type": "big",
   452  	})
   453  
   454  	cfg, err := config.New(config.NoDefaults, attrs)
   455  	c.Assert(err, gc.IsNil)
   456  	// Keep err for validation below.
   457  	valid, err := providerInstance.Validate(cfg, nil)
   458  	c.Assert(err, gc.IsNil)
   459  	// Check deprecated attributes removed.
   460  	allAttrs := valid.AllAttrs()
   461  	for _, attr := range []string{"default-image-id", "default-instance-type"} {
   462  		_, ok := allAttrs[attr]
   463  		c.Assert(ok, jc.IsFalse)
   464  	}
   465  }
   466  
   467  func (s *ConfigSuite) TestPrepareInsertsUniqueControlBucket(c *gc.C) {
   468  	s.setupEnvCredentials()
   469  	attrs := testing.FakeConfig().Merge(testing.Attrs{
   470  		"type": "openstack",
   471  	})
   472  	cfg, err := config.New(config.NoDefaults, attrs)
   473  	c.Assert(err, gc.IsNil)
   474  
   475  	ctx := testing.Context(c)
   476  	env0, err := providerInstance.Prepare(ctx, cfg)
   477  	c.Assert(err, gc.IsNil)
   478  	bucket0 := env0.(*environ).ecfg().controlBucket()
   479  	c.Assert(bucket0, gc.Matches, "[a-f0-9]{32}")
   480  
   481  	env1, err := providerInstance.Prepare(ctx, cfg)
   482  	c.Assert(err, gc.IsNil)
   483  	bucket1 := env1.(*environ).ecfg().controlBucket()
   484  	c.Assert(bucket1, gc.Matches, "[a-f0-9]{32}")
   485  
   486  	c.Assert(bucket1, gc.Not(gc.Equals), bucket0)
   487  }
   488  
   489  func (s *ConfigSuite) TestPrepareDoesNotTouchExistingControlBucket(c *gc.C) {
   490  	s.setupEnvCredentials()
   491  	attrs := testing.FakeConfig().Merge(testing.Attrs{
   492  		"type":           "openstack",
   493  		"control-bucket": "burblefoo",
   494  	})
   495  	cfg, err := config.New(config.NoDefaults, attrs)
   496  	c.Assert(err, gc.IsNil)
   497  
   498  	env, err := providerInstance.Prepare(testing.Context(c), cfg)
   499  	c.Assert(err, gc.IsNil)
   500  	bucket := env.(*environ).ecfg().controlBucket()
   501  	c.Assert(bucket, gc.Equals, "burblefoo")
   502  }
   503  
   504  func (s *ConfigSuite) setupEnvCredentials() {
   505  	os.Setenv("OS_USERNAME", "user")
   506  	os.Setenv("OS_PASSWORD", "secret")
   507  	os.Setenv("OS_AUTH_URL", "http://auth")
   508  	os.Setenv("OS_TENANT_NAME", "sometenant")
   509  	os.Setenv("OS_REGION_NAME", "region")
   510  }
   511  
   512  type ConfigDeprecationSuite struct {
   513  	ConfigSuite
   514  	writer *testWriter
   515  }
   516  
   517  var _ = gc.Suite(&ConfigDeprecationSuite{})
   518  
   519  func (s *ConfigDeprecationSuite) SetUpTest(c *gc.C) {
   520  	s.ConfigSuite.SetUpTest(c)
   521  }
   522  
   523  func (s *ConfigDeprecationSuite) TearDownTest(c *gc.C) {
   524  	s.ConfigSuite.TearDownTest(c)
   525  }
   526  
   527  func (s *ConfigDeprecationSuite) setupLogger(c *gc.C) {
   528  	var err error
   529  	s.writer = &testWriter{}
   530  	err = loggo.RegisterWriter("test", s.writer, loggo.WARNING)
   531  	c.Assert(err, gc.IsNil)
   532  }
   533  
   534  func (s *ConfigDeprecationSuite) resetLogger(c *gc.C) {
   535  	_, _, err := loggo.RemoveWriter("test")
   536  	c.Assert(err, gc.IsNil)
   537  }
   538  
   539  type testWriter struct {
   540  	messages []string
   541  }
   542  
   543  func (t *testWriter) Write(level loggo.Level, module, filename string, line int, timestamp time.Time, message string) {
   544  	t.messages = append(t.messages, message)
   545  }
   546  
   547  func (s *ConfigDeprecationSuite) setupEnv(c *gc.C, deprecatedKey, value string) {
   548  	s.setupEnvCredentials()
   549  	attrs := testing.FakeConfig().Merge(testing.Attrs{
   550  		"name":           "testenv",
   551  		"type":           "openstack",
   552  		"control-bucket": "x",
   553  		deprecatedKey:    value,
   554  	})
   555  	_, err := environs.NewFromAttrs(attrs)
   556  	c.Assert(err, gc.IsNil)
   557  }