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