Merge lp:~dimitern/juju-core/openstack-stub-provider-config into lp:~juju/juju-core/trunk
- openstack-stub-provider-config
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
The Go Language Gophers | Pending | ||
Review via email: mp+135454@code.launchpad.net |
Commit message
Implement environs/
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.
Dimiter Naydenov (dimitern) wrote : | # |
Roger Peppe (rogpeppe) wrote : | # |
looks great, with only minor comments below.
https:/
File environs/
https:/
environs/
perhaps these checks should be in dummyEnvAuth, and you could just
return err here. then the error message can be more useful.
https:/
File environs/
https:/
environs/
runner.
this is ubiquitous enough that it probably doesn't need a comment.
https:/
environs/
os.Getenv(
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.
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:/
File environs/
https:/
environs/
-------
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:/
environs/
auth/compute, etc. here
TODO(dimitern)
(we usually attach user names to TODOs)
https:/
environs/
as for Environ comment above.
https:/
environs/
*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.
Martin Packman (gz) wrote : | # |
Have made a selection of comments on specifics, some of which is just
for things to worry about in future.
https:/
File environs/
https:/
environs/
Where possible, use the existing keys names that python juju does, so
'auth-url' rather than 'identity-url' here.
https:/
environs/
string {
Using tenant-id vs. tenant-name is something we'll need to watch out for
later.
https:/
environs/
string {
As above, 'auth-url' instead.
https:/
environs/
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:/
environs/
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:/
environs/
{
Just use CredentialsFromEnv from the goose identity package as well.
https:/
File environs/
https:/
environs/
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:/
environs/
*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:/
environs/
I'd prefer not to bundle real canonistack values in here.
https:/
File environs/
https:/
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:/
File environs/
https:/
environs/
*C) {
See also what goose/testing/
alternative.
John A Meinel (jameinel) wrote : | # |
LGTM
Worth landing with some tweaks, and iterating from here.
https:/
File environs/
https:/
environs/
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:/
File environs/
https:/
environs/
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:/
environs/
*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:/
environs/
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...
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Martin Packman (gz) wrote : | # |
Changes all look good to me.
Gustavo Niemeyer (niemeyer) wrote : | # |
Nice. LGTM with trivials only:
https:/
File environs/
https:/
environs/
string {
As we discussed, let's use controlBucket/
setting, for compatibility with the ec2 provider.
https:/
environs/
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:/
environs/
no username, password, tenant-name, or auth-url")
"OpenStack environment requires options username, password, tenant-name,
and auth-url"
https:/
environs/
Why dummy? This looks quite real. I think it should be envAuth or
something like that.
https:/
environs/
{
getEnvAuth?
https:/
environs/
Same line as well, please.
https:/
environs/
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:/
File environs/
https:/
environs/
Nice tests.
Dimiter Naydenov (dimitern) wrote : | # |
https:/
File environs/
https:/
environs/
string {
On 2012/11/22 12:54:14, niemeyer wrote:
> As we discussed, let's use controlBucket/
setting, for
> compatibility with the ec2 provider.
Done.
https:/
environs/
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:/
environs/
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:/
environs/
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:/
environs/
{
On 2012/11/22 12:54:14, niemeyer wrote:
> getEnvAuth?
Done.
https:/
environs/
On 2012/11/22 12:54:14, niemeyer wrote:
> Same line as well, please.
Done.
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
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.
Dimiter Naydenov (dimitern) wrote : | # |
Please take a look.
Roger Peppe (rogpeppe) wrote : | # |
On 2012/11/22 15:03:10, dimitern wrote:
> Please take a look.
LGTM
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:/
Preview Diff
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 { |
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: openstack/ config. go openstack/ config_ test.go openstack/ provider. go
A [revision details]
A environs/
A environs/
A environs/