Merge lp:~dimitern/juju-core/openstack-stub-provider-config into lp:~juju/juju-core/trunk

Proposed by Dimiter Naydenov
Status: Merged
Merged at revision: 735
Proposed branch: lp:~dimitern/juju-core/openstack-stub-provider-config
Merge into: lp:~juju/juju-core/trunk
Diff against target: 603 lines (+575/-2)
4 files modified
environs/openstack/config.go (+141/-0)
environs/openstack/config_test.go (+287/-0)
environs/openstack/provider.go (+145/-0)
juju/bootstrap.go (+2/-2)
To merge this branch: bzr merge lp:~dimitern/juju-core/openstack-stub-provider-config
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+135454@code.launchpad.net

Commit message

Implement environs/openstack/config, tests and minimal stubbed OpenStack provider.

Description of the change

Implemented the base of a juju openstack provider.

Right now the configuration is parsed and validated, and the minimum amount of code needed in the provider is implemented, the rest is stubbed out for now.

https://codereview.appspot.com/6858052/

To post a comment you must log in.
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Reviewers: mp+135454_code.launchpad.net,

Message:
Please take a look.

Description:
Implemented the base of a juju openstack provider.

Right now the configuration is parsed and validated, and the minimum
amount of code needed in the provider is implemented, the rest is
stubbed out for now.

https://code.launchpad.net/~dimitern/juju-core/openstack-stub-provider-config/+merge/135454

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/6858052/

Affected files:
   A [revision details]
   A environs/openstack/config.go
   A environs/openstack/config_test.go
   A environs/openstack/provider.go

Revision history for this message
Roger Peppe (rogpeppe) wrote :

looks great, with only minor comments below.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go
File environs/openstack/config.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode79
environs/openstack/config.go:79: ecfg.username() != "" ||
perhaps these checks should be in dummyEnvAuth, and you could just
return err here. then the error message can be more useful.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go
File environs/openstack/config_test.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode18
environs/openstack/config_test.go:18: // Hook up gocheck into the gotest
runner.
this is ubiquitous enough that it probably doesn't need a comment.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode111
environs/openstack/config_test.go:111: s.savedUsername =
os.Getenv("OS_USERNAME")
there are perhaps enough of these that you could have a variable above,
say envVars:

var envVars = map[string]string{
     "OS_USERNAME" : "testuser",
     etc
}

then have ConfigSuite.savedVars defined as map[string]string

and below:

for v, val := range envVars {
    s.savedVargs[v] = os.Getenv(v)
    os.Setenv(v, val)
}

and restore in TearDownTest.

YMMV, just a thought.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go
File environs/openstack/provider.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go#newcode21
environs/openstack/provider.go:21: //
-------------------------------------------------------------------
d

we don't tend to bother with markers like this.
i'm not sure the comment below helps much either. it should be fairly
obvious that this is implementing environs.Environ.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go#newcode66
environs/openstack/provider.go:66: // TODO: setup the goose client
auth/compute, etc. here
TODO(dimitern)

(we usually attach user names to TODOs)

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go#newcode119
environs/openstack/provider.go:119: // EnvironProvider interface
as for Environ comment above.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go#newcode121
environs/openstack/provider.go:121: func (p environProvider) Open(cfg
*config.Config) (environs.Environ, error) {
i think it's worth putting these methods at the top of the file, as in
ec2 - an EnvironProvider is a precursor to the Environ, and it has many
less methods and much less code.

https://codereview.appspot.com/6858052/

Revision history for this message
Martin Packman (gz) wrote :
Download full text (3.3 KiB)

Have made a selection of comments on specifics, some of which is just
for things to worry about in future.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go
File environs/openstack/config.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode16
environs/openstack/config.go:16: "identity-url": schema.String(),
Where possible, use the existing keys names that python juju does, so
'auth-url' rather than 'identity-url' here.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode46
environs/openstack/config.go:46: func (c *environConfig) tenantId()
string {
Using tenant-id vs. tenant-name is something we'll need to watch out for
later.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode50
environs/openstack/config.go:50: func (c *environConfig) identityURL()
string {
As above, 'auth-url' instead.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode66
environs/openstack/config.go:66: func (p environProvider) Validate(cfg,
old *config.Config) (valid *config.Config, err error) {
Having one big function in the style of the current go code will get
painful with the various different authentication schemes openstack
uses, but we will want to delegate as much as possible to goose.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode115
environs/openstack/config.go:115: // TODO: temporarily here, until goose
client handles this
You should be able to use Credentials from the identity package, the
client has now been refactored to look at that.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode120
environs/openstack/config.go:120: func dummyEnvAuth() (dummyAuth, error)
{
Just use CredentialsFromEnv from the goose identity package as well.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go
File environs/openstack/config_test.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode40
environs/openstack/config_test.go:40: func (t configTest) check(c *C) {
This function is already big and scary and will presumably only get
worse when we add more authentication variations. Is there not a better
way?

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode110
environs/openstack/config_test.go:110: func (s *ConfigSuite) SetUpTest(c
*C) {
This is kind of ugly, and we'll need to do better for testing envvars as
there are a bunch of different ones beyond these. The tests for this in
the python code used a change_environment helper that you passed a dict
of new values to within the test which were then stashed and restored at
the end.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode132
environs/openstack/config_test.go:132: "region": "lcy01",
I'd prefer not to bundle real canonistack values in here.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go
File environs/openstack/provider.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/provider.go#newcode1...

Read more...

Revision history for this message
Martin Packman (gz) wrote :

Remembered that jam has already written a neat way of handling envvar
isolation in goose, that may want pulling out somewhere common so it can
be used here too.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go
File environs/openstack/config_test.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode110
environs/openstack/config_test.go:110: func (s *ConfigSuite) SetUpTest(c
*C) {
See also what goose/testing/envsuite provides, which is a neat
alternative.

https://codereview.appspot.com/6858052/

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.4 KiB)

LGTM
Worth landing with some tweaks, and iterating from here.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go
File environs/openstack/config.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config.go#newcode46
environs/openstack/config.go:46: func (c *environConfig) tenantId()
string {
On 2012/11/21 17:28:20, gz wrote:
> Using tenant-id vs. tenant-name is something we'll need to watch out
for later.

Just to follow up on this, "tenant-name" is what we'll get from the
environment, and what we would expect users to write themselves.
"tenant-id" would be something that we get from talking to the API.
So if we need to use it/store it we can, but ideally we would just go
with tenant name in the config.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go
File environs/openstack/config_test.go (right):

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode18
environs/openstack/config_test.go:18: // Hook up gocheck into the gotest
runner.
On 2012/11/21 17:26:12, rog wrote:
> this is ubiquitous enough that it probably doesn't need a comment.

I would also mention that I don't usually see 1-line functions. They
pretty much are always wrapped to 3 lines. Am I mistaken about that?

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode110
environs/openstack/config_test.go:110: func (s *ConfigSuite) SetUpTest(c
*C) {
On 2012/11/21 17:28:20, gz wrote:
> This is kind of ugly, and we'll need to do better for testing envvars
as there
> are a bunch of different ones beyond these. The tests for this in the
python
> code used a change_environment helper that you passed a dict of new
values to
> within the test which were then stashed and restored at the end.

If we switch to using CredentialsFromEnv, do we need a lot of env
variable testing here? It seems like permutation testing can be done at
the goose level, and then some sort of smoke test can be done here.

https://codereview.appspot.com/6858052/diff/1/environs/openstack/config_test.go#newcode130
environs/openstack/config_test.go:130: {
If we are going to go with table based tests instead of a bunch of named
tests, I'd like us to add summary: to the definitions, so that we have
some sort of name identifying what this permutation is trying to do.
Table based tests aren't that different from using a TestSuite that has
a common SetUp and TearDown and then you get a named test case for each
one. Hopefully these have less infrastructure making them easier to use,
but they do have the downside of not having human-sensible names unless
we put them there.

As a thought, I wonder if we might try writing this as TestSuite with
some helper setup functions, rather than as one big 'check' function.
And see which one is clearer. I can see where table based tests have
nice definitions. But you also tend to end up with a 'check' function
that mixes setup and assertions.

The 'check' function can become overloaded with lots of if checks,
because some cases want to test a failure mode, some want to test
setting a value, etc.

So there are bits I like (simple table definitio...

Read more...

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
Martin Packman (gz) wrote :

Changes all look good to me.

https://codereview.appspot.com/6858052/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

Nice. LGTM with trivials only:

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go
File environs/openstack/config.go (right):

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode54
environs/openstack/config.go:54: func (c *environConfig) container()
string {
As we discussed, let's use controlBucket/"control-bucket" for this
setting, for compatibility with the ec2 provider.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode75
environs/openstack/config.go:75: ecfg.authURL() == "" {
Can we please have these in the same line? We have lines around the same
width around here, and it ends up more reasonable as it doesn't align
with the internal block.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode79
environs/openstack/config.go:79: return nil, fmt.Errorf("environment has
no username, password, tenant-name, or auth-url")
"OpenStack environment requires options username, password, tenant-name,
and auth-url"

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode112
environs/openstack/config.go:112: type dummyAuth struct {
Why dummy? This looks quite real. I think it should be envAuth or
something like that.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode116
environs/openstack/config.go:116: func dummyEnvAuth() (dummyAuth, error)
{
getEnvAuth?

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode126
environs/openstack/config.go:126: auth.authURL == "" {
Same line as well, please.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode127
environs/openstack/config.go:127: return auth, fmt.Errorf("missing
username, password, tenant-name, or auth-url")
This function can be simplified to return (auth envAuth, ok bool)
instead. This error is pretty much the same one found above, and is
being discarded, which means the call site above is inferring what error
it is.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config_test.go
File environs/openstack/config_test.go (right):

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config_test.go#newcode133
environs/openstack/config_test.go:133: var configTests = []configTest{
Nice tests.

https://codereview.appspot.com/6858052/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go
File environs/openstack/config.go (right):

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode54
environs/openstack/config.go:54: func (c *environConfig) container()
string {
On 2012/11/22 12:54:14, niemeyer wrote:
> As we discussed, let's use controlBucket/"control-bucket" for this
setting, for
> compatibility with the ec2 provider.

Done.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode75
environs/openstack/config.go:75: ecfg.authURL() == "" {
On 2012/11/22 12:54:14, niemeyer wrote:
> Can we please have these in the same line? We have lines around the
same width
> around here, and it ends up more reasonable as it doesn't align with
the
> internal block.

Done.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode79
environs/openstack/config.go:79: return nil, fmt.Errorf("environment has
no username, password, tenant-name, or auth-url")
On 2012/11/22 12:54:14, niemeyer wrote:
> "OpenStack environment requires options username, password,
tenant-name, and
> auth-url"

Done.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode112
environs/openstack/config.go:112: type dummyAuth struct {
On 2012/11/22 12:54:14, niemeyer wrote:
> Why dummy? This looks quite real. I think it should be envAuth or
something like
> that.
This is here only because the goose identity package will handle this
eventually, but for now it's like this (hence the TODO), so we don't
couple the unstable code here. Anyway, renamed as suggested.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode116
environs/openstack/config.go:116: func dummyEnvAuth() (dummyAuth, error)
{
On 2012/11/22 12:54:14, niemeyer wrote:
> getEnvAuth?

Done.

https://codereview.appspot.com/6858052/diff/5002/environs/openstack/config.go#newcode126
environs/openstack/config.go:126: auth.authURL == "" {
On 2012/11/22 12:54:14, niemeyer wrote:
> Same line as well, please.

Done.

https://codereview.appspot.com/6858052/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
William Reade (fwereade) wrote :
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

On 2012/11/22 14:22:10, fwereade wrote:
> LGTM

As discussed, added checks for empty region and invalid auth-url.

https://codereview.appspot.com/6858052/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 2012/11/22 15:03:10, dimitern wrote:
> Please take a look.

LGTM

https://codereview.appspot.com/6858052/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

*** Submitted:

Implemented the base of a juju openstack provider.

Right now the configuration is parsed and validated, and the minimum
amount of code needed in the provider is implemented, the rest is
stubbed out for now.

R=rog, gz, john.meinel, niemeyer, fwereade
CC=
https://codereview.appspot.com/6858052

https://codereview.appspot.com/6858052/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'environs/openstack'
2=== added file 'environs/openstack/config.go'
3--- environs/openstack/config.go 1970-01-01 00:00:00 +0000
4+++ environs/openstack/config.go 2012-11-22 15:03:19 +0000
5@@ -0,0 +1,141 @@
6+package openstack
7+
8+import (
9+ "fmt"
10+ "launchpad.net/juju-core/environs/config"
11+ "launchpad.net/juju-core/schema"
12+ "net/url"
13+ "os"
14+)
15+
16+var configChecker = schema.StrictFieldMap(
17+ schema.Fields{
18+ "username": schema.String(),
19+ "password": schema.String(),
20+ "tenant-name": schema.String(),
21+ "auth-url": schema.String(),
22+ "region": schema.String(),
23+ "control-bucket": schema.String(),
24+ },
25+ schema.Defaults{
26+ "username": "",
27+ "password": "",
28+ "tenant-name": "",
29+ "auth-url": "",
30+ "region": "",
31+ "control-bucket": "",
32+ },
33+)
34+
35+type environConfig struct {
36+ *config.Config
37+ attrs map[string]interface{}
38+}
39+
40+func (c *environConfig) region() string {
41+ return c.attrs["region"].(string)
42+}
43+
44+func (c *environConfig) username() string {
45+ return c.attrs["username"].(string)
46+}
47+
48+func (c *environConfig) password() string {
49+ return c.attrs["password"].(string)
50+}
51+
52+func (c *environConfig) tenantName() string {
53+ return c.attrs["tenant-name"].(string)
54+}
55+
56+func (c *environConfig) authURL() string {
57+ return c.attrs["auth-url"].(string)
58+}
59+
60+func (c *environConfig) controlBucket() string {
61+ return c.attrs["control-bucket"].(string)
62+}
63+
64+func (p environProvider) newConfig(cfg *config.Config) (*environConfig, error) {
65+ valid, err := p.Validate(cfg, nil)
66+ if err != nil {
67+ return nil, err
68+ }
69+ return &environConfig{valid, valid.UnknownAttrs()}, nil
70+}
71+
72+func (p environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
73+ v, err := configChecker.Coerce(cfg.UnknownAttrs(), nil)
74+ if err != nil {
75+ return nil, err
76+ }
77+ ecfg := &environConfig{cfg, v.(map[string]interface{})}
78+
79+ if ecfg.authURL() != "" {
80+ parts, err := url.Parse(ecfg.authURL())
81+ if err != nil || parts.Host == "" || parts.Scheme == "" {
82+ return nil, fmt.Errorf("invalid auth-url value %q", ecfg.authURL())
83+ }
84+ }
85+
86+ if ecfg.username() == "" || ecfg.password() == "" || ecfg.tenantName() == "" || ecfg.authURL() == "" {
87+ // TODO(dimitern): get goose client to handle this
88+ auth, ok := getEnvAuth()
89+ if !ok {
90+ return nil, fmt.Errorf("OpenStack environment has no username, password, tenant-name, or auth-url")
91+ }
92+ ecfg.attrs["username"] = auth.username
93+ ecfg.attrs["password"] = auth.password
94+ ecfg.attrs["tenant-name"] = auth.tenantName
95+ ecfg.attrs["auth-url"] = auth.authURL
96+ }
97+ // We cannot validate the region name, since each OS installation
98+ // can have its own region names - only after authentication the
99+ // region names are known (from the service endpoints)
100+ if ecfg.region() == "" {
101+ region := os.Getenv("OS_REGION_NAME")
102+ if region != "" {
103+ ecfg.attrs["region"] = region
104+ } else {
105+ return nil, fmt.Errorf("OpenStack environment has no region")
106+ }
107+ }
108+
109+ if old != nil {
110+ attrs := old.UnknownAttrs()
111+ if region, _ := attrs["region"].(string); ecfg.region() != region {
112+ return nil, fmt.Errorf("cannot change region from %q to %q", region, ecfg.region())
113+ }
114+ if controlBucket, _ := attrs["control-bucket"].(string); ecfg.controlBucket() != controlBucket {
115+ return nil, fmt.Errorf("cannot change control-bucket from %q to %q", controlBucket, ecfg.controlBucket())
116+ }
117+ }
118+
119+ switch cfg.FirewallMode() {
120+ case config.FwDefault:
121+ ecfg.attrs["firewall-mode"] = config.FwInstance
122+ case config.FwInstance, config.FwGlobal:
123+ default:
124+ return nil, fmt.Errorf("unsupported firewall mode: %q", cfg.FirewallMode())
125+ }
126+
127+ return cfg.Apply(ecfg.attrs)
128+}
129+
130+// TODO(dimitern): temporarily here, until goose client handles this
131+type envAuth struct {
132+ username, password, tenantName, authURL string
133+}
134+
135+func getEnvAuth() (auth envAuth, ok bool) {
136+ auth = envAuth{
137+ username: os.Getenv("OS_USERNAME"),
138+ password: os.Getenv("OS_PASSWORD"),
139+ tenantName: os.Getenv("OS_TENANT_NAME"),
140+ authURL: os.Getenv("OS_AUTH_URL"),
141+ }
142+ if auth.username == "" || auth.password == "" || auth.tenantName == "" || auth.authURL == "" {
143+ return auth, false
144+ }
145+ return auth, true
146+}
147
148=== added file 'environs/openstack/config_test.go'
149--- environs/openstack/config_test.go 1970-01-01 00:00:00 +0000
150+++ environs/openstack/config_test.go 2012-11-22 15:03:19 +0000
151@@ -0,0 +1,287 @@
152+package openstack
153+
154+import (
155+ . "launchpad.net/gocheck"
156+ "launchpad.net/goyaml"
157+ "launchpad.net/juju-core/environs"
158+ "launchpad.net/juju-core/environs/config"
159+ "os"
160+ "testing"
161+)
162+
163+type ConfigSuite struct {
164+ savedVars map[string]string
165+}
166+
167+var envVars = map[string]string{
168+ "OS_USERNAME": "testuser",
169+ "OS_PASSWORD": "testpass",
170+ "OS_TENANT_NAME": "testtenant",
171+ "OS_AUTH_URL": "http://somehost",
172+ "OS_REGION_NAME": "testreg",
173+}
174+
175+var _ = Suite(&ConfigSuite{})
176+
177+func Test(t *testing.T) {
178+ TestingT(t)
179+}
180+
181+// configTest specifies a config parsing test, checking that env when
182+// parsed as the openstack section of a config file matches
183+// baseConfigResult when mutated by the mutate function, or that the
184+// parse matches the given error.
185+type configTest struct {
186+ summary string
187+ config attrs
188+ change attrs
189+ region string
190+ controlBucket string
191+ username string
192+ password string
193+ tenantName string
194+ authURL string
195+ firewallMode config.FirewallMode
196+ err string
197+}
198+
199+type attrs map[string]interface{}
200+
201+func (t configTest) check(c *C) {
202+ envs := attrs{
203+ "environments": attrs{
204+ "testenv": attrs{
205+ "type": "openstack",
206+ },
207+ },
208+ }
209+ testenv := envs["environments"].(attrs)["testenv"].(attrs)
210+ for k, v := range t.config {
211+ testenv[k] = v
212+ }
213+ if _, ok := testenv["control-bucket"]; !ok {
214+ testenv["control-bucket"] = "x"
215+ }
216+ data, err := goyaml.Marshal(envs)
217+ c.Assert(err, IsNil)
218+
219+ es, err := environs.ReadEnvironsBytes(data)
220+ c.Check(err, IsNil)
221+
222+ e, err := es.Open("testenv")
223+ if t.change != nil {
224+ c.Assert(err, IsNil)
225+
226+ // Testing a change in configuration.
227+ var old, changed, valid *config.Config
228+ osenv := e.(*environ)
229+ old = osenv.ecfg().Config
230+ changed, err = old.Apply(t.change)
231+ c.Assert(err, IsNil)
232+
233+ // Keep err for validation below.
234+ valid, err = providerInstance.Validate(changed, old)
235+ if err == nil {
236+ err = osenv.SetConfig(valid)
237+ }
238+ }
239+ if t.err != "" {
240+ c.Check(err, ErrorMatches, t.err)
241+ return
242+ }
243+ c.Assert(err, IsNil)
244+
245+ ecfg := e.(*environ).ecfg()
246+ c.Assert(ecfg.Name(), Equals, "testenv")
247+ c.Assert(ecfg.controlBucket(), Equals, "x")
248+ if t.region != "" {
249+ c.Assert(ecfg.region(), Equals, t.region)
250+ }
251+ if t.username != "" {
252+ c.Assert(ecfg.username(), Equals, t.username)
253+ c.Assert(ecfg.password(), Equals, t.password)
254+ c.Assert(ecfg.tenantName(), Equals, t.tenantName)
255+ c.Assert(ecfg.authURL(), Equals, t.authURL)
256+ expected := map[string]interface{}{
257+ "username": t.username,
258+ "password": t.password,
259+ "tenant-name": t.tenantName,
260+ }
261+ c.Assert(err, IsNil)
262+ actual, err := e.Provider().SecretAttrs(ecfg.Config)
263+ c.Assert(err, IsNil)
264+ c.Assert(expected, DeepEquals, actual)
265+ }
266+ if t.firewallMode != "" {
267+ c.Assert(ecfg.FirewallMode(), Equals, t.firewallMode)
268+ }
269+}
270+
271+func (s *ConfigSuite) SetUpTest(c *C) {
272+ s.savedVars = make(map[string]string)
273+ for v, val := range envVars {
274+ s.savedVars[v] = os.Getenv(v)
275+ os.Setenv(v, val)
276+ }
277+}
278+
279+func (s *ConfigSuite) TearDownTest(c *C) {
280+ for v, val := range envVars {
281+ os.Setenv(v, val)
282+ }
283+}
284+
285+var configTests = []configTest{
286+ {
287+ summary: "setting region",
288+ config: attrs{
289+ "region": "somereg",
290+ },
291+ region: "somereg",
292+ }, {
293+ summary: "setting region (2)",
294+ config: attrs{
295+ "region": "configtest",
296+ },
297+ region: "configtest",
298+ }, {
299+ summary: "changing region",
300+ config: attrs{
301+ "region": "configtest",
302+ },
303+ change: attrs{
304+ "region": "somereg",
305+ },
306+ err: `cannot change region from "configtest" to "somereg"`,
307+ }, {
308+ summary: "invalid region",
309+ config: attrs{
310+ "region": 666,
311+ },
312+ err: ".*expected string, got 666",
313+ }, {
314+ summary: "invalid username",
315+ config: attrs{
316+ "username": 666,
317+ },
318+ err: ".*expected string, got 666",
319+ }, {
320+ summary: "invalid password",
321+ config: attrs{
322+ "password": 666,
323+ },
324+ err: ".*expected string, got 666",
325+ }, {
326+ summary: "invalid tenant-name",
327+ config: attrs{
328+ "tenant-name": 666,
329+ },
330+ err: ".*expected string, got 666",
331+ }, {
332+ summary: "invalid auth-url type",
333+ config: attrs{
334+ "auth-url": 666,
335+ },
336+ err: ".*expected string, got 666",
337+ }, {
338+ summary: "invalid auth-url format",
339+ config: attrs{
340+ "auth-url": "invalid",
341+ },
342+ err: `invalid auth-url value "invalid"`,
343+ }, {
344+ summary: "invalid control-bucket",
345+ config: attrs{
346+ "control-bucket": 666,
347+ },
348+ err: ".*expected string, got 666",
349+ }, {
350+ summary: "changing control-bucket",
351+ change: attrs{
352+ "control-bucket": "new-x",
353+ },
354+ err: `cannot change control-bucket from "x" to "new-x"`,
355+ }, {
356+ summary: "valid auth args",
357+ config: attrs{
358+ "username": "jujuer",
359+ "password": "open sesame",
360+ "tenant-name": "juju tenant",
361+ "auth-url": "http://some/url",
362+ },
363+ username: "jujuer",
364+ password: "open sesame",
365+ tenantName: "juju tenant",
366+ authURL: "http://some/url",
367+ }, {
368+ summary: "admin-secret given",
369+ config: attrs{
370+ "admin-secret": "Futumpsh",
371+ },
372+ }, {
373+ summary: "default firewall-mode",
374+ config: attrs{},
375+ firewallMode: config.FwInstance,
376+ }, {
377+ summary: "unset firewall-mode",
378+ config: attrs{
379+ "firewall-mode": "",
380+ },
381+ firewallMode: config.FwInstance,
382+ }, {
383+ summary: "instance firewall-mode",
384+ config: attrs{
385+ "firewall-mode": "instance",
386+ },
387+ firewallMode: config.FwInstance,
388+ }, {
389+ summary: "global firewall-mode",
390+ config: attrs{
391+ "firewall-mode": "global",
392+ },
393+ firewallMode: config.FwGlobal,
394+ },
395+}
396+
397+func (s *ConfigSuite) TestConfig(c *C) {
398+ for i, t := range configTests {
399+ c.Logf("test %d: %s (%v)", i, t.summary, t.config)
400+ t.check(c)
401+ }
402+}
403+
404+func (s *ConfigSuite) TestMissingRegion(c *C) {
405+ os.Setenv("OS_REGION_NAME", "")
406+ test := configTests[0]
407+ delete(test.config, "region")
408+ test.err = ".*environment has no region"
409+ test.check(c)
410+}
411+
412+func (s *ConfigSuite) TestMissingUsername(c *C) {
413+ os.Setenv("OS_USERNAME", "")
414+ test := configTests[0]
415+ test.err = ".*environment has no username, password, tenant-name, or auth-url"
416+ test.check(c)
417+}
418+
419+func (s *ConfigSuite) TestMissingPassword(c *C) {
420+ os.Setenv("OS_PASSWORD", "")
421+ test := configTests[0]
422+ test.err = ".*environment has no username, password, tenant-name, or auth-url"
423+ test.check(c)
424+}
425+
426+func (s *ConfigSuite) TestMissinTenant(c *C) {
427+ os.Setenv("OS_TENANT_NAME", "")
428+ test := configTests[0]
429+ test.err = ".*environment has no username, password, tenant-name, or auth-url"
430+ test.check(c)
431+}
432+
433+func (s *ConfigSuite) TestMissingAuthUrl(c *C) {
434+ os.Setenv("OS_AUTH_URL", "")
435+ test := configTests[0]
436+ test.err = ".*environment has no username, password, tenant-name, or auth-url"
437+ test.check(c)
438+}
439
440=== added file 'environs/openstack/provider.go'
441--- environs/openstack/provider.go 1970-01-01 00:00:00 +0000
442+++ environs/openstack/provider.go 2012-11-22 15:03:19 +0000
443@@ -0,0 +1,145 @@
444+// Stub provider for OpenStack, using goose will be implemented here
445+
446+package openstack
447+
448+import (
449+ "launchpad.net/juju-core/environs"
450+ "launchpad.net/juju-core/environs/config"
451+ "launchpad.net/juju-core/log"
452+ "launchpad.net/juju-core/state"
453+ "sync"
454+)
455+
456+type environProvider struct{}
457+
458+var _ environs.EnvironProvider = (*environProvider)(nil)
459+
460+var providerInstance environProvider
461+
462+func init() {
463+ environs.RegisterProvider("openstack", environProvider{})
464+}
465+
466+func (p environProvider) Open(cfg *config.Config) (environs.Environ, error) {
467+ log.Printf("environs/openstack: opening environment %q", cfg.Name())
468+ e := new(environ)
469+ err := e.SetConfig(cfg)
470+ if err != nil {
471+ return nil, err
472+ }
473+ return e, nil
474+}
475+
476+func (p environProvider) SecretAttrs(cfg *config.Config) (map[string]interface{}, error) {
477+ m := make(map[string]interface{})
478+ ecfg, err := providerInstance.newConfig(cfg)
479+ if err != nil {
480+ return nil, err
481+ }
482+ m["username"] = ecfg.username()
483+ m["password"] = ecfg.password()
484+ m["tenant-name"] = ecfg.tenantName()
485+ return m, nil
486+}
487+
488+func (p environProvider) PublicAddress() (string, error) {
489+ panic("not implemented")
490+}
491+
492+func (p environProvider) PrivateAddress() (string, error) {
493+ panic("not implemented")
494+}
495+
496+type environ struct {
497+ name string
498+
499+ ecfgMutex sync.Mutex
500+ ecfgUnlocked *environConfig
501+}
502+
503+var _ environs.Environ = (*environ)(nil)
504+
505+func (e *environ) ecfg() *environConfig {
506+ e.ecfgMutex.Lock()
507+ ecfg := e.ecfgUnlocked
508+ e.ecfgMutex.Unlock()
509+ return ecfg
510+}
511+
512+func (e *environ) Name() string {
513+ return e.name
514+}
515+
516+func (e *environ) Bootstrap(uploadTools bool, stateServerPEM []byte) error {
517+ panic("not implemented")
518+}
519+
520+func (e *environ) StateInfo() (*state.Info, error) {
521+ panic("not implemented")
522+}
523+
524+func (e *environ) Config() *config.Config {
525+ panic("not implemented")
526+}
527+
528+func (e *environ) SetConfig(cfg *config.Config) error {
529+ ecfg, err := providerInstance.newConfig(cfg)
530+ if err != nil {
531+ return err
532+ }
533+ e.ecfgMutex.Lock()
534+ defer e.ecfgMutex.Unlock()
535+ e.name = ecfg.Name()
536+ e.ecfgUnlocked = ecfg
537+
538+ // TODO(dimitern): setup the goose client auth/compute, etc. here
539+ return nil
540+}
541+
542+func (e *environ) StartInstance(machineId int, info *state.Info, tools *state.Tools) (environs.Instance, error) {
543+ panic("not implemented")
544+}
545+
546+func (e *environ) StopInstances([]environs.Instance) error {
547+ panic("not implemented")
548+}
549+
550+func (e *environ) Instances(ids []string) ([]environs.Instance, error) {
551+ panic("not implemented")
552+}
553+
554+func (e *environ) AllInstances() ([]environs.Instance, error) {
555+ panic("not implemented")
556+}
557+
558+func (e *environ) Storage() environs.Storage {
559+ panic("not implemented")
560+}
561+
562+func (e *environ) PublicStorage() environs.StorageReader {
563+ panic("not implemented")
564+}
565+
566+func (e *environ) Destroy(insts []environs.Instance) error {
567+ panic("not implemented")
568+}
569+
570+func (e *environ) AssignmentPolicy() state.AssignmentPolicy {
571+ panic("not implemented")
572+}
573+
574+func (e *environ) OpenPorts(ports []state.Port) error {
575+ panic("not implemented")
576+}
577+
578+func (e *environ) ClosePorts(ports []state.Port) error {
579+ panic("not implemented")
580+}
581+
582+func (e *environ) Ports() ([]state.Port, error) {
583+ panic("not implemented")
584+}
585+
586+func (e *environ) Provider() environs.EnvironProvider {
587+ return &providerInstance
588+}
589
590=== modified file 'juju/bootstrap.go'
591--- juju/bootstrap.go 2012-11-21 00:49:11 +0000
592+++ juju/bootstrap.go 2012-11-22 15:03:19 +0000
593@@ -74,8 +74,8 @@
594 SubjectKeyId: bigIntHash(priv.N),
595 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
596 BasicConstraintsValid: true,
597- IsCA: true,
598- MaxPathLen: 0, // Disallow delegation for now.
599+ IsCA: true,
600+ MaxPathLen: 0, // Disallow delegation for now.
601 }
602 certDER, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
603 if err != nil {

Subscribers

People subscribed via source and target branches