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 }