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 }