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