Merge lp:~maxim-perenesenko/juju-core/cloudsigma into lp:~go-bot/juju-core/trunk
- cloudsigma
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~maxim-perenesenko/juju-core/cloudsigma |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
3777 lines (+3644/-1) 23 files modified
cmd/jujud/machine.go (+3/-1) dependencies.tsv (+1/-0) provider/all/all.go (+1/-0) provider/cloudsigma/client.go (+307/-0) provider/cloudsigma/client_test.go (+316/-0) provider/cloudsigma/config.go (+165/-0) provider/cloudsigma/config_test.go (+329/-0) provider/cloudsigma/constraints.go (+95/-0) provider/cloudsigma/constraints_test.go (+136/-0) provider/cloudsigma/environ.go (+164/-0) provider/cloudsigma/environ_test.go (+96/-0) provider/cloudsigma/environcaps.go (+50/-0) provider/cloudsigma/environfirewall.go (+32/-0) provider/cloudsigma/environinstance.go (+291/-0) provider/cloudsigma/environinstance_test.go (+206/-0) provider/cloudsigma/instance.go (+148/-0) provider/cloudsigma/instance_test.go (+328/-0) provider/cloudsigma/provider.go (+140/-0) provider/cloudsigma/provider_test.go (+94/-0) provider/cloudsigma/storage.go (+252/-0) provider/cloudsigma/storage_test.go (+373/-0) provider/cloudsigma/storageconfig.go (+59/-0) provider/cloudsigma/storageconfig_test.go (+58/-0) |
To merge this branch: | bzr merge lp:~maxim-perenesenko/juju-core/cloudsigma |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+221540@code.launchpad.net |
Commit message
Description of the change
Added provider for CloudSigma cloud.
Provider for CloudSigma (https:/
(https:/
Minimal client for communicating with the CloudSigma Web API from Go program was implemented as
separate library github.
gosigma library is added as dependency to dependencies.tsv file.
- 2676. By Maxim Perenesenko
-
merged r2823
- 2677. By Maxim Perenesenko
-
fix tests after merge & issues from https:/
/codereview. appspot. com/101970045/ #msg2
Unmerged revisions
- 2677. By Maxim Perenesenko
-
fix tests after merge & issues from https:/
/codereview. appspot. com/101970045/ #msg2 - 2676. By Maxim Perenesenko
-
merged r2823
- 2675. By Maxim Perenesenko
-
dependencies.tsv updated, added entry for gosigma library
- 2674. By Maxim Perenesenko
-
merged r2811
- 2673. By Maxim Perenesenko
-
vet & lint
- 2672. By Maxim Perenesenko
-
cloudsigma provider: changed value of default cpu-power
- 2671. By Maxim Perenesenko
-
cloudsigma constraints tests
- 2670. By Maxim Perenesenko
-
cloudsigma test for new remote storage
- 2669. By Maxim Perenesenko
-
cloudsigma tests added
- 2668. By Maxim Perenesenko
-
merged r2799
Preview Diff
1 | === modified file 'cmd/jujud/machine.go' |
2 | --- cmd/jujud/machine.go 2014-06-02 21:26:40 +0000 |
3 | +++ cmd/jujud/machine.go 2014-06-09 11:10:33 +0000 |
4 | @@ -433,7 +433,9 @@ |
5 | // Take advantage of special knowledge here in that we will only ever want |
6 | // the storage provider on one machine, and that is the "bootstrap" node. |
7 | providerType := agentConfig.Value(agent.ProviderType) |
8 | - if (providerType == provider.Local || provider.IsManual(providerType)) && m.Id() == bootstrapMachineId { |
9 | + if (providerType == provider.Local || |
10 | + provider.IsManual(providerType) || |
11 | + providerType == "cloudsigma") && m.Id() == bootstrapMachineId { |
12 | a.startWorkerAfterUpgrade(runner, "local-storage", func() (worker.Worker, error) { |
13 | // TODO(axw) 2013-09-24 bug #1229507 |
14 | // Make another job to enable storage. |
15 | |
16 | === modified file 'dependencies.tsv' |
17 | --- dependencies.tsv 2014-06-02 15:47:39 +0000 |
18 | +++ dependencies.tsv 2014-06-09 11:10:33 +0000 |
19 | @@ -1,5 +1,6 @@ |
20 | code.google.com/p/go.crypto hg 6478cc9340cbbe6c04511280c5007722269108e9 184 |
21 | code.google.com/p/go.net hg c17ad62118ea511e1051721b429779fa40bddc74 116 |
22 | +github.com/Altoros/gosigma git c56c52eb785e48ec0f7fcc0da385c85210c7ff3d |
23 | github.com/binary132/gojsonpointer git 57ab5e9c764219a3e0c4d7759797fefdcab22e9c |
24 | github.com/binary132/gojsonreference git 75785fb7b21f9bf2051dca600da83ff57bc6582a |
25 | github.com/binary132/gojsonschema git 640782bf48d45ba2b22fd1e8a80842667c52af00 |
26 | |
27 | === modified file 'provider/all/all.go' |
28 | --- provider/all/all.go 2014-04-02 11:35:49 +0000 |
29 | +++ provider/all/all.go 2014-06-09 11:10:33 +0000 |
30 | @@ -6,6 +6,7 @@ |
31 | // Register all the available providers. |
32 | import ( |
33 | _ "launchpad.net/juju-core/provider/azure" |
34 | + _ "launchpad.net/juju-core/provider/cloudsigma" |
35 | _ "launchpad.net/juju-core/provider/ec2" |
36 | _ "launchpad.net/juju-core/provider/joyent" |
37 | _ "launchpad.net/juju-core/provider/local" |
38 | |
39 | === added directory 'provider/cloudsigma' |
40 | === added file 'provider/cloudsigma/client.go' |
41 | --- provider/cloudsigma/client.go 1970-01-01 00:00:00 +0000 |
42 | +++ provider/cloudsigma/client.go 2014-06-09 11:10:33 +0000 |
43 | @@ -0,0 +1,307 @@ |
44 | +// Copyright 2014 Canonical Ltd. |
45 | +// Licensed under the AGPLv3, see LICENCE file for details. |
46 | + |
47 | +package cloudsigma |
48 | + |
49 | +import ( |
50 | + "fmt" |
51 | + "launchpad.net/juju-core/environs" |
52 | + "launchpad.net/juju-core/instance" |
53 | + "launchpad.net/juju-core/utils" |
54 | + "strings" |
55 | + |
56 | + "github.com/Altoros/gosigma" |
57 | + "github.com/juju/loggo" |
58 | +) |
59 | + |
60 | +// This file contains implementation of CloudSigma client. |
61 | +type environClient struct { |
62 | + conn *gosigma.Client |
63 | + region string |
64 | + username string |
65 | + password string |
66 | + name string |
67 | + storage *environStorage |
68 | +} |
69 | + |
70 | +type tracer struct{} |
71 | + |
72 | +func (tracer) Logf(format string, args ...interface{}) { |
73 | + logger.Tracef(format, args...) |
74 | +} |
75 | + |
76 | +// newClient creates new CloudSigma client connection. |
77 | +var newClient = func(cfg *environConfig) (*environClient, error) { |
78 | + |
79 | + // fetch and validate configuration |
80 | + region, err := gosigma.ResolveEndpoint(cfg.region()) |
81 | + if err != nil { |
82 | + region = cfg.region() |
83 | + } |
84 | + username := cfg.username() |
85 | + password := cfg.password() |
86 | + name := cfg.Name() |
87 | + |
88 | + logger.Debugf("creating CloudSigma client: region=%s, user=%s, password=%s, name=%q", |
89 | + region, username, strings.Repeat("*", len(password)), name) |
90 | + |
91 | + // create connection to CloudSigma |
92 | + conn, err := gosigma.NewClient(region, username, password, nil) |
93 | + if err != nil { |
94 | + return nil, err |
95 | + } |
96 | + |
97 | + // configure trace logger |
98 | + if logger.LogLevel() <= loggo.TRACE { |
99 | + conn.Logger(&tracer{}) |
100 | + } |
101 | + |
102 | + c := &environClient{ |
103 | + conn: conn, |
104 | + region: region, |
105 | + name: name, |
106 | + username: username, |
107 | + password: password, |
108 | + } |
109 | + |
110 | + return c, nil |
111 | +} |
112 | + |
113 | +// configChanged checks if CloudSigma client environment configuration is changed |
114 | +func (c environClient) configChanged(cfg *environConfig) bool { |
115 | + // fetch configuration |
116 | + region, err := gosigma.ResolveEndpoint(cfg.region()) |
117 | + if err != nil { |
118 | + return true |
119 | + } |
120 | + username := cfg.username() |
121 | + password := cfg.password() |
122 | + name := cfg.Name() |
123 | + |
124 | + // compare |
125 | + if region != c.region || username != c.username || |
126 | + password != c.password || name != c.name { |
127 | + return true |
128 | + } |
129 | + |
130 | + return false |
131 | +} |
132 | + |
133 | +const ( |
134 | + jujuMetaInstance = "juju-instance" |
135 | + jujuMetaInstanceStateServer = "state-server" |
136 | + jujuMetaInstanceServer = "server" |
137 | + |
138 | + jujuMetaEnvironment = "juju-environment" |
139 | +) |
140 | + |
141 | +func (c environClient) isMyEnvironment(s gosigma.Server) bool { |
142 | + if v, _ := s.Get(jujuMetaEnvironment); c.name == v { |
143 | + return true |
144 | + } |
145 | + return false |
146 | +} |
147 | + |
148 | +func (c environClient) isMyServer(s gosigma.Server) bool { |
149 | + if _, ok := s.Get(jujuMetaInstance); ok { |
150 | + return c.isMyEnvironment(s) |
151 | + } |
152 | + return false |
153 | +} |
154 | + |
155 | +func (c environClient) isMyStateServer(s gosigma.Server) bool { |
156 | + if v, ok := s.Get(jujuMetaInstance); ok && v == jujuMetaInstanceStateServer { |
157 | + return c.isMyEnvironment(s) |
158 | + } |
159 | + return false |
160 | +} |
161 | + |
162 | +// instances of servers at CloudSigma account |
163 | +func (c environClient) instances() ([]gosigma.Server, error) { |
164 | + return c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer) |
165 | +} |
166 | + |
167 | +// instanceMap of server ids to servers at CloudSigma account |
168 | +func (c environClient) instanceMap() (map[string]gosigma.Server, error) { |
169 | + servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer) |
170 | + if err != nil { |
171 | + return nil, err |
172 | + } |
173 | + |
174 | + m := make(map[string]gosigma.Server, len(servers)) |
175 | + for _, s := range servers { |
176 | + m[s.UUID()] = s |
177 | + } |
178 | + |
179 | + return m, nil |
180 | +} |
181 | + |
182 | +func (c environClient) stateServerAddress() (string, string, bool) { |
183 | + logger.Debugf("query state...") |
184 | + |
185 | + servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyStateServer) |
186 | + if err != nil { |
187 | + return "", "", false |
188 | + } |
189 | + |
190 | + logger.Debugf("...servers count: %d", len(servers)) |
191 | + if logger.LogLevel() <= loggo.TRACE { |
192 | + for _, s := range servers { |
193 | + logger.Tracef("... %s", s) |
194 | + } |
195 | + } |
196 | + |
197 | + for _, s := range servers { |
198 | + if s.Status() != gosigma.ServerRunning { |
199 | + continue |
200 | + } |
201 | + if addrs := s.IPv4(); len(addrs) != 0 { |
202 | + return s.UUID(), addrs[0], true |
203 | + } |
204 | + } |
205 | + |
206 | + return "", "", false |
207 | +} |
208 | + |
209 | +// stop instance |
210 | +func (c environClient) stopInstance(id instance.Id) error { |
211 | + uuid := string(id) |
212 | + if uuid == "" { |
213 | + return fmt.Errorf("invalid instance id") |
214 | + } |
215 | + |
216 | + var err error |
217 | + |
218 | + s, err := c.conn.Server(uuid) |
219 | + if err != nil { |
220 | + return err |
221 | + } |
222 | + |
223 | + if c.storage != nil && c.isMyStateServer(s) { |
224 | + c.storage.onStateInstanceStop(uuid) |
225 | + } |
226 | + |
227 | + err = s.StopWait() |
228 | + logger.Tracef("environClient.StopInstance - stop server, %q = %v", uuid, err) |
229 | + |
230 | + err = s.Remove(gosigma.RecurseAllDrives) |
231 | + logger.Tracef("environClient.StopInstance - remove server, %q = %v", uuid, err) |
232 | + |
233 | + return nil |
234 | +} |
235 | + |
236 | +// start new instance |
237 | +func (c environClient) newInstance(args environs.StartInstanceParams) (srv gosigma.Server, drv gosigma.Drive, err error) { |
238 | + |
239 | + cleanup := func() { |
240 | + if err == nil { |
241 | + return |
242 | + } |
243 | + if srv != nil { |
244 | + srv.Remove(gosigma.RecurseAllDrives) |
245 | + } else if drv != nil { |
246 | + drv.Remove() |
247 | + } |
248 | + srv = nil |
249 | + drv = nil |
250 | + } |
251 | + defer cleanup() |
252 | + |
253 | + if args.MachineConfig == nil { |
254 | + err = fmt.Errorf("invalid configuration for new instance") |
255 | + return |
256 | + } |
257 | + |
258 | + logger.Tracef("Tools: %v", args.Tools.URLs()) |
259 | + logger.Tracef("Juju Constraints:" + args.Constraints.String()) |
260 | + logger.Tracef("MachineConfig: %#v", args.MachineConfig) |
261 | + |
262 | + cs, err := newConstraints(args.MachineConfig.Bootstrap, |
263 | + args.Constraints, args.MachineConfig.Tools.Version.Series) |
264 | + if err != nil { |
265 | + return |
266 | + } |
267 | + logger.Debugf("CloudSigma Constraints: %v", cs) |
268 | + |
269 | + originalDrive, err := c.conn.Drive(cs.driveTemplate, gosigma.LibraryMedia) |
270 | + if err != nil { |
271 | + err = fmt.Errorf("query drive template: %v", err) |
272 | + return |
273 | + } |
274 | + |
275 | + baseName := "juju-" + c.name + "-" + args.MachineConfig.MachineId |
276 | + |
277 | + cloneParams := gosigma.CloneParams{Name: baseName} |
278 | + if drv, err = originalDrive.CloneWait(cloneParams, nil); err != nil { |
279 | + err = fmt.Errorf("error cloning drive: %v", err) |
280 | + return |
281 | + } |
282 | + |
283 | + if drv.Size() < cs.driveSize { |
284 | + if err = drv.ResizeWait(cs.driveSize); err != nil { |
285 | + err = fmt.Errorf("error resizing drive: %v", err) |
286 | + return |
287 | + } |
288 | + } |
289 | + |
290 | + var cc gosigma.Components |
291 | + cc.SetName(baseName) |
292 | + cc.SetDescription(baseName) |
293 | + |
294 | + if cs.cores != 0 { |
295 | + cc.SetSMP(cs.cores) |
296 | + } |
297 | + |
298 | + if cs.power != 0 { |
299 | + cc.SetCPU(cs.power) |
300 | + } |
301 | + |
302 | + if cs.mem != 0 { |
303 | + cc.SetMem(cs.mem) |
304 | + } |
305 | + |
306 | + vncpass, err := utils.RandomPassword() |
307 | + if err != nil { |
308 | + err = fmt.Errorf("error generating password: %v", err) |
309 | + return |
310 | + } |
311 | + cc.SetVNCPassword(vncpass) |
312 | + |
313 | + cc.SetSSHPublicKey(args.MachineConfig.AuthorizedKeys) |
314 | + cc.AttachDrive(1, "0:0", "virtio", drv.UUID()) |
315 | + cc.NetworkDHCP4(gosigma.ModelVirtio) |
316 | + |
317 | + if args.MachineConfig.Bootstrap { |
318 | + cc.SetMeta(jujuMetaInstance, jujuMetaInstanceStateServer) |
319 | + } else { |
320 | + cc.SetMeta(jujuMetaInstance, jujuMetaInstanceServer) |
321 | + } |
322 | + |
323 | + if c.name != "" { |
324 | + cc.SetMeta(jujuMetaEnvironment, c.name) |
325 | + } |
326 | + |
327 | + if srv, err = c.conn.CreateServer(cc); err != nil { |
328 | + err = fmt.Errorf("error creating new instance: %v", err) |
329 | + return |
330 | + } |
331 | + |
332 | + if err = srv.StartWait(); err != nil { |
333 | + err = fmt.Errorf("error booting new instance: %v", err) |
334 | + return |
335 | + } |
336 | + |
337 | + var ipaddr string |
338 | + instanceNetworkAvailable := func(s gosigma.Server) bool { |
339 | + ipaddr = sigmaInstance{s}.findIPv4() |
340 | + return ipaddr != "" |
341 | + } |
342 | + if err = srv.Wait(instanceNetworkAvailable); err != nil { |
343 | + err = fmt.Errorf("error waiting for instance IP address: %v", err) |
344 | + return |
345 | + } |
346 | + |
347 | + logger.Tracef("instance ip %q", ipaddr) |
348 | + |
349 | + return |
350 | +} |
351 | |
352 | === added file 'provider/cloudsigma/client_test.go' |
353 | --- provider/cloudsigma/client_test.go 1970-01-01 00:00:00 +0000 |
354 | +++ provider/cloudsigma/client_test.go 2014-06-09 11:10:33 +0000 |
355 | @@ -0,0 +1,316 @@ |
356 | +// Copyright 2014 Canonical Ltd. |
357 | +// Licensed under the AGPLv3, see LICENCE file for details. |
358 | + |
359 | +package cloudsigma |
360 | + |
361 | +import ( |
362 | + "fmt" |
363 | + "strings" |
364 | + "time" |
365 | + |
366 | + "github.com/Altoros/gosigma" |
367 | + "github.com/Altoros/gosigma/data" |
368 | + "github.com/Altoros/gosigma/mock" |
369 | + "github.com/juju/loggo" |
370 | + gc "launchpad.net/gocheck" |
371 | + "launchpad.net/juju-core/constraints" |
372 | + "launchpad.net/juju-core/environs" |
373 | + "launchpad.net/juju-core/environs/cloudinit" |
374 | + "launchpad.net/juju-core/instance" |
375 | + "launchpad.net/juju-core/testing" |
376 | + "launchpad.net/juju-core/tools" |
377 | + "launchpad.net/juju-core/version" |
378 | +) |
379 | + |
380 | +type clientSuite struct { |
381 | + testing.BaseSuite |
382 | +} |
383 | + |
384 | +var _ = gc.Suite(&clientSuite{}) |
385 | + |
386 | +func (s *clientSuite) SetUpSuite(c *gc.C) { |
387 | + s.BaseSuite.SetUpSuite(c) |
388 | + mock.Start() |
389 | +} |
390 | + |
391 | +func (s *clientSuite) TearDownSuite(c *gc.C) { |
392 | + mock.Stop() |
393 | + s.BaseSuite.TearDownSuite(c) |
394 | +} |
395 | + |
396 | +func (s *clientSuite) SetUpTest(c *gc.C) { |
397 | + s.BaseSuite.SetUpTest(c) |
398 | + |
399 | + ll := logger.LogLevel() |
400 | + logger.SetLogLevel(loggo.TRACE) |
401 | + s.AddCleanup(func(*gc.C) { logger.SetLogLevel(ll) }) |
402 | + |
403 | + mock.Reset() |
404 | +} |
405 | + |
406 | +func (s *clientSuite) TearDownTest(c *gc.C) { |
407 | + mock.Reset() |
408 | + s.BaseSuite.TearDownTest(c) |
409 | +} |
410 | + |
411 | +func testNewClient(c *gc.C, endpoint, username, password string) (*environClient, error) { |
412 | + ecfg := &environConfig{ |
413 | + Config: newConfig(c, testing.Attrs{"name": "client-test"}), |
414 | + attrs: map[string]interface{}{ |
415 | + "region": endpoint, |
416 | + "username": username, |
417 | + "password": password, |
418 | + }, |
419 | + } |
420 | + return newClient(ecfg) |
421 | +} |
422 | + |
423 | +func (s *clientSuite) TestClientNew(c *gc.C) { |
424 | + cli, err := testNewClient(c, "https://testing.invalid", "user", "password") |
425 | + c.Check(err, gc.IsNil) |
426 | + c.Check(cli, gc.NotNil) |
427 | + |
428 | + cli, err = testNewClient(c, "http://testing.invalid", "user", "password") |
429 | + c.Check(err, gc.ErrorMatches, "endpoint must use https scheme") |
430 | + c.Check(cli, gc.IsNil) |
431 | + |
432 | + cli, err = testNewClient(c, "https://testing.invalid", "", "password") |
433 | + c.Check(err, gc.ErrorMatches, "username is not allowed to be empty") |
434 | + c.Check(cli, gc.IsNil) |
435 | +} |
436 | + |
437 | +func (s *clientSuite) TestClientConfigChanged(c *gc.C) { |
438 | + ecfg := &environConfig{ |
439 | + Config: newConfig(c, testing.Attrs{"name": "client-test"}), |
440 | + attrs: map[string]interface{}{ |
441 | + "region": "https://testing.invalid", |
442 | + "username": "user", |
443 | + "password": "password", |
444 | + }, |
445 | + } |
446 | + |
447 | + cli, err := newClient(ecfg) |
448 | + c.Check(err, gc.IsNil) |
449 | + c.Check(cli, gc.NotNil) |
450 | + |
451 | + rc := cli.configChanged(ecfg) |
452 | + c.Check(rc, gc.Equals, false) |
453 | + |
454 | + ecfg.attrs["region"] = "" |
455 | + rc = cli.configChanged(ecfg) |
456 | + c.Check(rc, gc.Equals, true) |
457 | + |
458 | + ecfg.attrs["region"] = "https://testing.invalid" |
459 | + ecfg.attrs["username"] = "user1" |
460 | + rc = cli.configChanged(ecfg) |
461 | + c.Check(rc, gc.Equals, true) |
462 | + |
463 | + ecfg.attrs["username"] = "user" |
464 | + ecfg.attrs["password"] = "password1" |
465 | + rc = cli.configChanged(ecfg) |
466 | + c.Check(rc, gc.Equals, true) |
467 | + |
468 | + ecfg.attrs["password"] = "password" |
469 | + ecfg.Config = newConfig(c, testing.Attrs{"name": "changed"}) |
470 | + rc = cli.configChanged(ecfg) |
471 | + c.Check(rc, gc.Equals, true) |
472 | +} |
473 | + |
474 | +func addTestClientServer(c *gc.C, instance, env, ip string) string { |
475 | + json := `{"meta": {` |
476 | + if instance != "" { |
477 | + json += fmt.Sprintf(`"juju-instance": "%s"`, instance) |
478 | + if env != "" { |
479 | + json += fmt.Sprintf(`, "juju-environment": "%s"`, env) |
480 | + } |
481 | + } |
482 | + json += fmt.Sprintf(`}, "status": "running", "nics":[{ |
483 | + "runtime": { |
484 | + "interface_type": "public", |
485 | + "ip_v4": { |
486 | + "resource_uri": "/api/2.0/ips/%s/", |
487 | + "uuid": "%s" |
488 | + }}}]}`, ip, ip) |
489 | + r := strings.NewReader(json) |
490 | + s, err := data.ReadServer(r) |
491 | + c.Assert(err, gc.IsNil) |
492 | + mock.AddServer(s) |
493 | + return s.UUID |
494 | +} |
495 | + |
496 | +func (s *clientSuite) TestClientInstances(c *gc.C) { |
497 | + addTestClientServer(c, "", "", "") |
498 | + addTestClientServer(c, jujuMetaInstanceServer, "alien", "") |
499 | + addTestClientServer(c, jujuMetaInstanceStateServer, "alien", "") |
500 | + addTestClientServer(c, jujuMetaInstanceServer, "client-test", "1.1.1.1") |
501 | + addTestClientServer(c, jujuMetaInstanceServer, "client-test", "2.2.2.2") |
502 | + suuid := addTestClientServer(c, jujuMetaInstanceStateServer, "client-test", "3.3.3.3") |
503 | + |
504 | + cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword) |
505 | + c.Assert(err, gc.IsNil) |
506 | + |
507 | + ss, err := cli.instances() |
508 | + c.Assert(err, gc.IsNil) |
509 | + c.Assert(ss, gc.NotNil) |
510 | + c.Check(ss, gc.HasLen, 3) |
511 | + |
512 | + sm, err := cli.instanceMap() |
513 | + c.Assert(err, gc.IsNil) |
514 | + c.Assert(sm, gc.NotNil) |
515 | + c.Check(sm, gc.HasLen, 3) |
516 | + |
517 | + uuid, ip, rc := cli.stateServerAddress() |
518 | + c.Check(uuid, gc.Equals, suuid) |
519 | + c.Check(ip, gc.Equals, "3.3.3.3") |
520 | + c.Check(rc, gc.Equals, true) |
521 | +} |
522 | + |
523 | +func (s *clientSuite) TestClientStopStateInstance(c *gc.C) { |
524 | + addTestClientServer(c, "", "", "") |
525 | + addTestClientServer(c, jujuMetaInstanceServer, "alien", "") |
526 | + addTestClientServer(c, jujuMetaInstanceStateServer, "alien", "") |
527 | + addTestClientServer(c, jujuMetaInstanceServer, "client-test", "1.1.1.1") |
528 | + addTestClientServer(c, jujuMetaInstanceServer, "client-test", "2.2.2.2") |
529 | + suuid := addTestClientServer(c, jujuMetaInstanceStateServer, "client-test", "3.3.3.3") |
530 | + |
531 | + cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword) |
532 | + c.Assert(err, gc.IsNil) |
533 | + |
534 | + cli.storage = &environStorage{uuid: suuid, tmp: true} |
535 | + |
536 | + err = cli.stopInstance(instance.Id(suuid)) |
537 | + c.Assert(err, gc.IsNil) |
538 | + |
539 | + uuid, ip, rc := cli.stateServerAddress() |
540 | + c.Check(uuid, gc.Equals, "") |
541 | + c.Check(ip, gc.Equals, "") |
542 | + c.Check(rc, gc.Equals, false) |
543 | + |
544 | + c.Check(cli.storage.tmp, gc.Equals, false) |
545 | +} |
546 | + |
547 | +func (s *clientSuite) TestClientInvalidStopInstance(c *gc.C) { |
548 | + cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword) |
549 | + c.Assert(err, gc.IsNil) |
550 | + |
551 | + var id instance.Id |
552 | + err = cli.stopInstance(id) |
553 | + c.Check(err, gc.ErrorMatches, "invalid instance id") |
554 | + |
555 | + err = cli.stopInstance("1234") |
556 | + c.Check(err, gc.ErrorMatches, "404 Not Found.*") |
557 | +} |
558 | + |
559 | +func (s *clientSuite) TestClientInvalidServer(c *gc.C) { |
560 | + cli, err := testNewClient(c, "https://testing.invalid", mock.TestUser, mock.TestPassword) |
561 | + c.Assert(err, gc.IsNil) |
562 | + |
563 | + cli.conn.ConnectTimeout(10 * time.Millisecond) |
564 | + |
565 | + err = cli.stopInstance("1234") |
566 | + c.Check(err, gc.ErrorMatches, "broken connection") |
567 | + |
568 | + _, err = cli.instanceMap() |
569 | + c.Check(err, gc.ErrorMatches, "broken connection") |
570 | + |
571 | + uuid, ip, ok := cli.stateServerAddress() |
572 | + c.Check(uuid, gc.Equals, "") |
573 | + c.Check(ip, gc.Equals, "") |
574 | + c.Check(ok, gc.Equals, false) |
575 | +} |
576 | + |
577 | +func (s *clientSuite) TestClientNewInstanceInvalidParams(c *gc.C) { |
578 | + cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword) |
579 | + c.Assert(err, gc.IsNil) |
580 | + |
581 | + params := environs.StartInstanceParams{ |
582 | + Constraints: constraints.Value{}, |
583 | + } |
584 | + server, drive, err := cli.newInstance(params) |
585 | + c.Check(server, gc.IsNil) |
586 | + c.Check(drive, gc.IsNil) |
587 | + c.Check(err, gc.ErrorMatches, "invalid configuration for new instance") |
588 | + |
589 | + params.MachineConfig = &cloudinit.MachineConfig{ |
590 | + Bootstrap: true, |
591 | + Tools: &tools.Tools{ |
592 | + Version: version.Binary{ |
593 | + Series: "series", |
594 | + }, |
595 | + }, |
596 | + } |
597 | + server, drive, err = cli.newInstance(params) |
598 | + c.Check(server, gc.IsNil) |
599 | + c.Check(drive, gc.IsNil) |
600 | + c.Check(err, gc.ErrorMatches, "series 'series' not supported") |
601 | + |
602 | + var arch = "arch" |
603 | + params.Constraints.Arch = &arch |
604 | + server, drive, err = cli.newInstance(params) |
605 | + c.Check(server, gc.IsNil) |
606 | + c.Check(drive, gc.IsNil) |
607 | + c.Check(err, gc.ErrorMatches, "arch 'arch' not supported") |
608 | +} |
609 | + |
610 | +func (s *clientSuite) TestClientNewInstanceInvalidTemplate(c *gc.C) { |
611 | + cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword) |
612 | + c.Assert(err, gc.IsNil) |
613 | + |
614 | + params := environs.StartInstanceParams{ |
615 | + Constraints: constraints.Value{}, |
616 | + MachineConfig: &cloudinit.MachineConfig{ |
617 | + Bootstrap: true, |
618 | + Tools: &tools.Tools{ |
619 | + Version: version.Binary{ |
620 | + Series: "trusty", |
621 | + }, |
622 | + }, |
623 | + }, |
624 | + } |
625 | + server, drive, err := cli.newInstance(params) |
626 | + c.Check(server, gc.IsNil) |
627 | + c.Check(drive, gc.IsNil) |
628 | + c.Check(err, gc.ErrorMatches, "query drive template: 404 Not Found.*") |
629 | +} |
630 | + |
631 | +func (s *clientSuite) TestClientNewInstance(c *gc.C) { |
632 | + cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword) |
633 | + c.Assert(err, gc.IsNil) |
634 | + |
635 | + cli.conn.OperationTimeout(1 * time.Second) |
636 | + |
637 | + params := environs.StartInstanceParams{ |
638 | + Constraints: constraints.Value{}, |
639 | + MachineConfig: &cloudinit.MachineConfig{ |
640 | + Bootstrap: true, |
641 | + Tools: &tools.Tools{ |
642 | + Version: version.Binary{ |
643 | + Series: "trusty", |
644 | + }, |
645 | + }, |
646 | + }, |
647 | + } |
648 | + cs, err := newConstraints(params.MachineConfig.Bootstrap, |
649 | + params.Constraints, params.MachineConfig.Tools.Version.Series) |
650 | + c.Assert(cs, gc.NotNil) |
651 | + c.Check(err, gc.IsNil) |
652 | + |
653 | + templateDrive := &data.Drive{ |
654 | + Resource: data.Resource{URI: "uri", UUID: cs.driveTemplate}, |
655 | + LibraryDrive: data.LibraryDrive{ |
656 | + Arch: "arch", |
657 | + ImageType: "image-type", |
658 | + OS: "os", |
659 | + Paid: true, |
660 | + }, |
661 | + Size: 2200 * gosigma.Megabyte, |
662 | + Status: "unmounted", |
663 | + } |
664 | + mock.ResetDrives() |
665 | + mock.LibDrives.Add(templateDrive) |
666 | + |
667 | + server, drive, err := cli.newInstance(params) |
668 | + c.Check(server, gc.NotNil) |
669 | + c.Check(drive, gc.NotNil) |
670 | + c.Check(err, gc.IsNil) |
671 | +} |
672 | |
673 | === added file 'provider/cloudsigma/config.go' |
674 | --- provider/cloudsigma/config.go 1970-01-01 00:00:00 +0000 |
675 | +++ provider/cloudsigma/config.go 2014-06-09 11:10:33 +0000 |
676 | @@ -0,0 +1,165 @@ |
677 | +// Copyright 2014 Canonical Ltd. |
678 | +// Licensed under the AGPLv3, see LICENCE file for details. |
679 | + |
680 | +package cloudsigma |
681 | + |
682 | +import ( |
683 | + "fmt" |
684 | + "github.com/Altoros/gosigma" |
685 | + |
686 | + "launchpad.net/juju-core/environs/config" |
687 | + "launchpad.net/juju-core/schema" |
688 | + "launchpad.net/juju-core/utils" |
689 | +) |
690 | + |
691 | +// boilerplateConfig will be shown in help output, so please keep it up to |
692 | +// date when you change environment configuration below. |
693 | +var boilerplateConfig = `# https://juju.ubuntu.com/docs/config-cloudsigma.html |
694 | +cloudsigma: |
695 | + type: cloudsigma |
696 | + |
697 | + # region holds the cloudsigma region (zrh, lvs, ...). |
698 | + # |
699 | + # region: <your region> |
700 | + |
701 | + # credentials for CloudSigma account |
702 | + # |
703 | + # username: <your username> |
704 | + # password: <secret> |
705 | + |
706 | + # storage-port specifes the TCP port that the |
707 | + # bootstrap machine's Juju storage server will listen |
708 | + # on. It defaults to ` + fmt.Sprint(defaultStoragePort) + ` |
709 | + # |
710 | + # storage-port: ` + fmt.Sprint(defaultStoragePort) + ` |
711 | + |
712 | +` |
713 | + |
714 | +const defaultStoragePort = 8040 |
715 | + |
716 | +var configFields = schema.Fields{ |
717 | + "username": schema.String(), |
718 | + "password": schema.String(), |
719 | + "region": schema.String(), |
720 | + |
721 | + "storage-port": schema.ForceInt(), |
722 | + "storage-auth-key": schema.String(), |
723 | +} |
724 | + |
725 | +var configDefaultFields = schema.Defaults{ |
726 | + "username": "", |
727 | + "password": "", |
728 | + "region": gosigma.DefaultRegion, |
729 | + |
730 | + "storage-port": defaultStoragePort, |
731 | +} |
732 | + |
733 | +var configSecretFields = []string{ |
734 | + "storage-auth-key", |
735 | +} |
736 | + |
737 | +var configImmutableFields = []string{ |
738 | + "storage-port", |
739 | + "storage-auth-key", |
740 | + "region", |
741 | +} |
742 | + |
743 | +func prepareConfig(cfg *config.Config) (*config.Config, error) { |
744 | + // Turn an incomplete config into a valid one, if possible. |
745 | + attrs := cfg.UnknownAttrs() |
746 | + |
747 | + if _, ok := attrs["storage-auth-key"]; !ok { |
748 | + uuid, err := utils.NewUUID() |
749 | + if err != nil { |
750 | + return nil, err |
751 | + } |
752 | + attrs["storage-auth-key"] = uuid.String() |
753 | + } |
754 | + |
755 | + return cfg.Apply(attrs) |
756 | +} |
757 | + |
758 | +func validateConfig(cfg *config.Config, old *environConfig) (*environConfig, error) { |
759 | + // Check sanity of juju-level fields. |
760 | + var oldCfg *config.Config |
761 | + if old != nil { |
762 | + oldCfg = old.Config |
763 | + } |
764 | + if err := config.Validate(cfg, oldCfg); err != nil { |
765 | + return nil, err |
766 | + } |
767 | + |
768 | + // Extract validated provider-specific fields. All of configFields will be |
769 | + // present in validated, and defaults will be inserted if necessary. If the |
770 | + // schema you passed in doesn't quite express what you need, you can make |
771 | + // whatever checks you need here, before continuing. |
772 | + // In particular, if you want to extract (say) credentials from the user's |
773 | + // shell environment variables, you'll need to allow missing values to pass |
774 | + // through the schema by setting a value of schema.Omit in the configFields |
775 | + // map, and then to set and check them at this point. These values *must* be |
776 | + // stored in newAttrs: a Config will be generated on the user's machine only |
777 | + // to begin with, and will subsequently be used on a different machine that |
778 | + // will probably not have those variables set. |
779 | + newAttrs, err := cfg.ValidateUnknownAttrs(configFields, configDefaultFields) |
780 | + if err != nil { |
781 | + return nil, err |
782 | + } |
783 | + for field := range configFields { |
784 | + if newAttrs[field] == "" { |
785 | + return nil, fmt.Errorf("%s: must not be empty", field) |
786 | + } |
787 | + } |
788 | + |
789 | + // If an old config was supplied, check any immutable fields have not changed. |
790 | + if old != nil { |
791 | + for _, field := range configImmutableFields { |
792 | + if old.attrs[field] != newAttrs[field] { |
793 | + return nil, fmt.Errorf( |
794 | + "%s: cannot change from %v to %v", |
795 | + field, old.attrs[field], newAttrs[field], |
796 | + ) |
797 | + } |
798 | + } |
799 | + } |
800 | + |
801 | + // Merge the validated provider-specific fields into the original config, |
802 | + // to ensure the object we return is internally consistent. |
803 | + newCfg, err := cfg.Apply(newAttrs) |
804 | + if err != nil { |
805 | + return nil, err |
806 | + } |
807 | + ecfg := &environConfig{ |
808 | + Config: newCfg, |
809 | + attrs: newAttrs, |
810 | + } |
811 | + |
812 | + return ecfg, nil |
813 | +} |
814 | + |
815 | +type environConfig struct { |
816 | + *config.Config |
817 | + attrs map[string]interface{} |
818 | +} |
819 | + |
820 | +func (c environConfig) region() string { |
821 | + return c.attrs["region"].(string) |
822 | +} |
823 | + |
824 | +func (c environConfig) username() string { |
825 | + return c.attrs["username"].(string) |
826 | +} |
827 | + |
828 | +func (c environConfig) password() string { |
829 | + return c.attrs["password"].(string) |
830 | +} |
831 | + |
832 | +func (c environConfig) storagePort() int { |
833 | + return c.attrs["storage-port"].(int) |
834 | +} |
835 | + |
836 | +func (c environConfig) storageAuthKey() string { |
837 | + if v, ok := c.attrs["storage-auth-key"].(string); ok { |
838 | + return v |
839 | + } |
840 | + return "" |
841 | +} |
842 | |
843 | === added file 'provider/cloudsigma/config_test.go' |
844 | --- provider/cloudsigma/config_test.go 1970-01-01 00:00:00 +0000 |
845 | +++ provider/cloudsigma/config_test.go 2014-06-09 11:10:33 +0000 |
846 | @@ -0,0 +1,329 @@ |
847 | +// Copyright 2014 Canonical Ltd. |
848 | +// Licensed under the AGPLv3, see LICENCE file for details. |
849 | + |
850 | +package cloudsigma |
851 | + |
852 | +import ( |
853 | + "crypto/rand" |
854 | + "fmt" |
855 | + gc "launchpad.net/gocheck" |
856 | + |
857 | + "launchpad.net/juju-core/environs" |
858 | + "launchpad.net/juju-core/environs/config" |
859 | + "launchpad.net/juju-core/schema" |
860 | + "launchpad.net/juju-core/testing" |
861 | +) |
862 | + |
863 | +func newConfig(c *gc.C, attrs testing.Attrs) *config.Config { |
864 | + attrs = testing.FakeConfig().Merge(attrs) |
865 | + cfg, err := config.New(config.UseDefaults, attrs) |
866 | + c.Assert(err, gc.IsNil) |
867 | + return cfg |
868 | +} |
869 | + |
870 | +func validAttrs() testing.Attrs { |
871 | + return testing.FakeConfig().Merge(testing.Attrs{ |
872 | + "type": "cloudsigma", |
873 | + "username": "user", |
874 | + "password": "password", |
875 | + "region": "zrh", |
876 | + "storage-port": 8040, |
877 | + "storage-auth-key": "ABCDEFGH", |
878 | + }) |
879 | +} |
880 | + |
881 | +type configSuite struct { |
882 | + testing.BaseSuite |
883 | +} |
884 | + |
885 | +func (s *configSuite) SetUpSuite(c *gc.C) { |
886 | + s.BaseSuite.SetUpSuite(c) |
887 | +} |
888 | + |
889 | +func (s *configSuite) SetUpTest(c *gc.C) { |
890 | + s.BaseSuite.SetUpTest(c) |
891 | + // speed up tests, do not create heavy stuff inside providers created withing this test suite |
892 | + s.PatchValue(&newClient, func(cfg *environConfig) (*environClient, error) { |
893 | + return nil, nil |
894 | + }) |
895 | + s.PatchValue(&newStorage, func(ecfg *environConfig, client *environClient) (*environStorage, error) { |
896 | + return nil, nil |
897 | + }) |
898 | +} |
899 | + |
900 | +var _ = gc.Suite(&configSuite{}) |
901 | + |
902 | +func (s *configSuite) TestNewEnvironConfig(c *gc.C) { |
903 | + |
904 | + type checker struct { |
905 | + checker gc.Checker |
906 | + value interface{} |
907 | + } |
908 | + |
909 | + var newConfigTests = []struct { |
910 | + info string |
911 | + insert testing.Attrs |
912 | + remove []string |
913 | + expect testing.Attrs |
914 | + err string |
915 | + }{{ |
916 | + info: "username is required", |
917 | + remove: []string{"username"}, |
918 | + err: "username: must not be empty", |
919 | + }, { |
920 | + info: "username cannot be empty", |
921 | + insert: testing.Attrs{"username": ""}, |
922 | + err: "username: must not be empty", |
923 | + }, { |
924 | + info: "password is required", |
925 | + remove: []string{"password"}, |
926 | + err: "password: must not be empty", |
927 | + }, { |
928 | + info: "password cannot be empty", |
929 | + insert: testing.Attrs{"password": ""}, |
930 | + err: "password: must not be empty", |
931 | + }, { |
932 | + info: "region is inserted if missing", |
933 | + remove: []string{"region"}, |
934 | + expect: testing.Attrs{"region": "zrh"}, |
935 | + }, { |
936 | + info: "region must not be empty", |
937 | + insert: testing.Attrs{"region": ""}, |
938 | + err: "region: must not be empty", |
939 | + }, { |
940 | + info: "storage-port is inserted if missing", |
941 | + remove: []string{"storage-port"}, |
942 | + expect: testing.Attrs{"storage-port": 8040}, |
943 | + }, { |
944 | + info: "storage-port must be number", |
945 | + insert: testing.Attrs{"storage-port": "abcd"}, |
946 | + err: "storage-port: expected number, got string\\(\"abcd\"\\)", |
947 | + }, { |
948 | + info: "storage-auth-key is inserted if missing", |
949 | + remove: []string{"storage-auth-key"}, |
950 | + expect: testing.Attrs{"storage-auth-key": checker{gc.HasLen, 36}}, |
951 | + }, { |
952 | + info: "storage-auth-key must not be empty", |
953 | + insert: testing.Attrs{"storage-auth-key": ""}, |
954 | + err: "storage-auth-key: must not be empty", |
955 | + }} |
956 | + |
957 | + for i, test := range newConfigTests { |
958 | + c.Logf("test %d: %s", i, test.info) |
959 | + attrs := validAttrs().Merge(test.insert).Delete(test.remove...) |
960 | + testConfig := newConfig(c, attrs) |
961 | + environ, err := environs.New(testConfig) |
962 | + if test.err == "" { |
963 | + c.Check(err, gc.IsNil) |
964 | + attrs := environ.Config().AllAttrs() |
965 | + for field, value := range test.expect { |
966 | + if chk, ok := value.(checker); ok { |
967 | + c.Check(attrs[field], chk.checker, chk.value) |
968 | + } else { |
969 | + c.Check(attrs[field], gc.Equals, value) |
970 | + } |
971 | + } |
972 | + } else { |
973 | + c.Check(environ, gc.IsNil) |
974 | + c.Check(err, gc.ErrorMatches, test.err) |
975 | + } |
976 | + } |
977 | +} |
978 | + |
979 | +var changeConfigTests = []struct { |
980 | + info string |
981 | + insert testing.Attrs |
982 | + remove []string |
983 | + expect testing.Attrs |
984 | + err string |
985 | +}{{ |
986 | + info: "no change, no error", |
987 | + expect: validAttrs(), |
988 | +}, { |
989 | + info: "can change username", |
990 | + insert: testing.Attrs{"username": "cloudsigma_user"}, |
991 | + expect: testing.Attrs{"username": "cloudsigma_user"}, |
992 | +}, { |
993 | + info: "can not change username to empty", |
994 | + insert: testing.Attrs{"username": ""}, |
995 | + err: "username: must not be empty", |
996 | +}, { |
997 | + info: "can change password", |
998 | + insert: testing.Attrs{"password": "cloudsigma_password"}, |
999 | + expect: testing.Attrs{"password": "cloudsigma_password"}, |
1000 | +}, { |
1001 | + info: "can not change password to empty", |
1002 | + insert: testing.Attrs{"password": ""}, |
1003 | + err: "password: must not be empty", |
1004 | +}, { |
1005 | + info: "can change region", |
1006 | + insert: testing.Attrs{"region": "lvs"}, |
1007 | + err: "region: cannot change from .* to .*", |
1008 | +}, { |
1009 | + info: "can not change storage-port", |
1010 | + insert: testing.Attrs{"storage-port": 0}, |
1011 | + err: "storage-port: cannot change from .* to .*", |
1012 | +}, { |
1013 | + info: "can not change storage-auth-key", |
1014 | + insert: testing.Attrs{"storage-auth-key": "xxx"}, |
1015 | + err: "storage-auth-key: cannot change from .* to .*", |
1016 | +}} |
1017 | + |
1018 | +func (s *configSuite) TestValidateChange(c *gc.C) { |
1019 | + |
1020 | + baseConfig := newConfig(c, validAttrs()) |
1021 | + for i, test := range changeConfigTests { |
1022 | + c.Logf("test %d: %s", i, test.info) |
1023 | + attrs := validAttrs().Merge(test.insert).Delete(test.remove...) |
1024 | + testConfig := newConfig(c, attrs) |
1025 | + validatedConfig, err := providerInstance.Validate(testConfig, baseConfig) |
1026 | + if test.err == "" { |
1027 | + c.Check(err, gc.IsNil) |
1028 | + attrs := validatedConfig.AllAttrs() |
1029 | + for field, value := range test.expect { |
1030 | + c.Check(attrs[field], gc.Equals, value) |
1031 | + } |
1032 | + } else { |
1033 | + c.Check(validatedConfig, gc.IsNil) |
1034 | + c.Check(err, gc.ErrorMatches, "invalid config.*: "+test.err) |
1035 | + } |
1036 | + |
1037 | + // reverse change |
1038 | + validatedConfig, err = providerInstance.Validate(baseConfig, testConfig) |
1039 | + if test.err == "" { |
1040 | + c.Check(err, gc.IsNil) |
1041 | + attrs := validatedConfig.AllAttrs() |
1042 | + for field, value := range validAttrs() { |
1043 | + c.Check(attrs[field], gc.Equals, value) |
1044 | + } |
1045 | + } else { |
1046 | + c.Check(validatedConfig, gc.IsNil) |
1047 | + c.Check(err, gc.ErrorMatches, "invalid .*config.*: "+test.err) |
1048 | + } |
1049 | + } |
1050 | +} |
1051 | + |
1052 | +func (s *configSuite) TestSetConfig(c *gc.C) { |
1053 | + baseConfig := newConfig(c, validAttrs()) |
1054 | + for i, test := range changeConfigTests { |
1055 | + c.Logf("test %d: %s", i, test.info) |
1056 | + environ, err := environs.New(baseConfig) |
1057 | + c.Assert(err, gc.IsNil) |
1058 | + attrs := validAttrs().Merge(test.insert).Delete(test.remove...) |
1059 | + testConfig := newConfig(c, attrs) |
1060 | + err = environ.SetConfig(testConfig) |
1061 | + newAttrs := environ.Config().AllAttrs() |
1062 | + if test.err == "" { |
1063 | + c.Check(err, gc.IsNil) |
1064 | + for field, value := range test.expect { |
1065 | + c.Check(newAttrs[field], gc.Equals, value) |
1066 | + } |
1067 | + } else { |
1068 | + c.Check(err, gc.ErrorMatches, test.err) |
1069 | + for field, value := range baseConfig.UnknownAttrs() { |
1070 | + c.Check(newAttrs[field], gc.Equals, value) |
1071 | + } |
1072 | + } |
1073 | + } |
1074 | +} |
1075 | + |
1076 | +func (s *configSuite) TestConfigName(c *gc.C) { |
1077 | + baseConfig := newConfig(c, validAttrs().Merge(testing.Attrs{"name": "testname"})) |
1078 | + environ, err := environs.New(baseConfig) |
1079 | + c.Assert(err, gc.IsNil) |
1080 | + c.Check(environ.Name(), gc.Equals, "testname") |
1081 | +} |
1082 | + |
1083 | +func (s *configSuite) TestBadUUIDGenerator(c *gc.C) { |
1084 | + fail := failReader{fmt.Errorf("error")} |
1085 | + s.PatchValue(&rand.Reader, &fail) |
1086 | + |
1087 | + attrs := validAttrs().Delete("storage-auth-key") |
1088 | + testConfig := newConfig(c, attrs) |
1089 | + cfg, err := providerInstance.Prepare(nil, testConfig) |
1090 | + |
1091 | + c.Check(cfg, gc.IsNil) |
1092 | + c.Check(err, gc.Equals, fail.err) |
1093 | +} |
1094 | + |
1095 | +func (s *configSuite) TestEnvironConfig(c *gc.C) { |
1096 | + testConfig := newConfig(c, validAttrs()) |
1097 | + ecfg, err := validateConfig(testConfig, nil) |
1098 | + c.Assert(ecfg, gc.NotNil) |
1099 | + c.Assert(err, gc.IsNil) |
1100 | + c.Check(ecfg.username(), gc.Equals, "user") |
1101 | + c.Check(ecfg.password(), gc.Equals, "password") |
1102 | + c.Check(ecfg.region(), gc.Equals, "zrh") |
1103 | + c.Check(ecfg.storagePort(), gc.Equals, 8040) |
1104 | + c.Check(ecfg.storageAuthKey(), gc.Equals, "ABCDEFGH") |
1105 | +} |
1106 | + |
1107 | +func (s *configSuite) TestInvalidConfigChange(c *gc.C) { |
1108 | + oldAttrs := validAttrs().Merge(testing.Attrs{"name": "123"}) |
1109 | + oldConfig := newConfig(c, oldAttrs) |
1110 | + newAttrs := validAttrs().Merge(testing.Attrs{"name": "321"}) |
1111 | + newConfig := newConfig(c, newAttrs) |
1112 | + |
1113 | + oldecfg, _ := providerInstance.Validate(oldConfig, nil) |
1114 | + c.Assert(oldecfg, gc.NotNil) |
1115 | + |
1116 | + newecfg, err := providerInstance.Validate(newConfig, oldecfg) |
1117 | + c.Assert(newecfg, gc.IsNil) |
1118 | + c.Assert(err, gc.NotNil) |
1119 | +} |
1120 | + |
1121 | +var secretAttrsConfigTests = []struct { |
1122 | + info string |
1123 | + insert testing.Attrs |
1124 | + remove []string |
1125 | + expect map[string]string |
1126 | + err string |
1127 | +}{{ |
1128 | + info: "no change, no error", |
1129 | + expect: map[string]string{"storage-auth-key": "ABCDEFGH"}, |
1130 | +}, { |
1131 | + info: "invalid config", |
1132 | + insert: testing.Attrs{"username": ""}, |
1133 | + err: ".* must not be empty.*", |
1134 | +}} |
1135 | + |
1136 | +func (s *configSuite) TestSecretAttrs(c *gc.C) { |
1137 | + for i, test := range secretAttrsConfigTests { |
1138 | + c.Logf("test %d: %s", i, test.info) |
1139 | + attrs := validAttrs().Merge(test.insert).Delete(test.remove...) |
1140 | + testConfig := newConfig(c, attrs) |
1141 | + sa, err := providerInstance.SecretAttrs(testConfig) |
1142 | + if test.err == "" { |
1143 | + c.Check(sa, gc.HasLen, len(test.expect)) |
1144 | + for field, value := range test.expect { |
1145 | + c.Check(sa[field], gc.Equals, value) |
1146 | + } |
1147 | + c.Check(err, gc.IsNil) |
1148 | + } else { |
1149 | + c.Check(sa, gc.IsNil) |
1150 | + c.Check(err, gc.ErrorMatches, test.err) |
1151 | + } |
1152 | + } |
1153 | +} |
1154 | + |
1155 | +func (s *configSuite) TestSecretAttrsAreStrings(c *gc.C) { |
1156 | + for i, field := range configSecretFields { |
1157 | + c.Logf("test %d: %s", i, field) |
1158 | + attrs := validAttrs().Merge(testing.Attrs{field: 0}) |
1159 | + |
1160 | + if v, ok := configFields[field]; ok { |
1161 | + configFields[field] = schema.ForceInt() |
1162 | + defer func(c schema.Checker) { |
1163 | + configFields[field] = c |
1164 | + }(v) |
1165 | + } else { |
1166 | + c.Errorf("secrect field %s not found in configFields", field) |
1167 | + continue |
1168 | + } |
1169 | + |
1170 | + testConfig := newConfig(c, attrs) |
1171 | + sa, err := providerInstance.SecretAttrs(testConfig) |
1172 | + c.Check(sa, gc.IsNil) |
1173 | + c.Check(err, gc.ErrorMatches, "secret .* field must have a string value; got .*") |
1174 | + } |
1175 | +} |
1176 | |
1177 | === added file 'provider/cloudsigma/constraints.go' |
1178 | --- provider/cloudsigma/constraints.go 1970-01-01 00:00:00 +0000 |
1179 | +++ provider/cloudsigma/constraints.go 2014-06-09 11:10:33 +0000 |
1180 | @@ -0,0 +1,95 @@ |
1181 | +// Copyright 2014 Canonical Ltd. |
1182 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1183 | + |
1184 | +package cloudsigma |
1185 | + |
1186 | +import ( |
1187 | + "fmt" |
1188 | + |
1189 | + "github.com/Altoros/gosigma" |
1190 | + "launchpad.net/juju-core/constraints" |
1191 | +) |
1192 | + |
1193 | +// This file contains implementation of CloudSigma instance constraints |
1194 | +type sigmaConstraints struct { |
1195 | + driveTemplate string |
1196 | + driveSize uint64 |
1197 | + cores uint64 |
1198 | + power uint64 |
1199 | + mem uint64 |
1200 | +} |
1201 | + |
1202 | +const defaultCPUPower = 2000 |
1203 | +const driveUbuntuTrusty64 = "473adb38-3b64-43b2-93bd-f1a3443c19ea" |
1204 | + |
1205 | +// newConstraints creates new CloudSigma constraints from juju common constraints |
1206 | +func newConstraints(bootstrap bool, jc constraints.Value, series string) (*sigmaConstraints, error) { |
1207 | + var sc sigmaConstraints |
1208 | + |
1209 | + if a := jc.Arch; a != nil { |
1210 | + switch *a { |
1211 | + case "amd64": |
1212 | + switch series { |
1213 | + case "trusty": |
1214 | + sc.driveTemplate = driveUbuntuTrusty64 |
1215 | + default: |
1216 | + return nil, fmt.Errorf("series '%s' not supported", series) |
1217 | + } |
1218 | + default: |
1219 | + return nil, fmt.Errorf("arch '%s' not supported", *a) |
1220 | + } |
1221 | + } else { |
1222 | + switch series { |
1223 | + case "trusty": |
1224 | + sc.driveTemplate = driveUbuntuTrusty64 |
1225 | + default: |
1226 | + return nil, fmt.Errorf("series '%s' not supported", series) |
1227 | + } |
1228 | + } |
1229 | + |
1230 | + if size := jc.RootDisk; bootstrap && size == nil { |
1231 | + sc.driveSize = 5 * gosigma.Gigabyte |
1232 | + } else if size != nil { |
1233 | + sc.driveSize = *size * gosigma.Megabyte |
1234 | + } |
1235 | + |
1236 | + if c := jc.CpuCores; c != nil { |
1237 | + sc.cores = *c |
1238 | + } else { |
1239 | + sc.cores = 1 |
1240 | + } |
1241 | + |
1242 | + if p := jc.CpuPower; p != nil { |
1243 | + sc.power = *p |
1244 | + } else { |
1245 | + if sc.cores == 1 { |
1246 | + // The default of cpu power is 2000 Mhz |
1247 | + sc.power = defaultCPUPower |
1248 | + } else { |
1249 | + // The maximum amount of cpu per smp is 2300 |
1250 | + sc.power = sc.cores * defaultCPUPower |
1251 | + } |
1252 | + } |
1253 | + |
1254 | + if m := jc.Mem; m != nil { |
1255 | + sc.mem = *m * gosigma.Megabyte |
1256 | + } else { |
1257 | + sc.mem = 2 * gosigma.Gigabyte |
1258 | + } |
1259 | + |
1260 | + return &sc, nil |
1261 | +} |
1262 | + |
1263 | +func (c *sigmaConstraints) String() string { |
1264 | + s := fmt.Sprintf("template=%s,drive=%dG", c.driveTemplate, c.driveSize/gosigma.Gigabyte) |
1265 | + if c.cores > 0 { |
1266 | + s += fmt.Sprintf(",cores=%d", c.cores) |
1267 | + } |
1268 | + if c.power > 0 { |
1269 | + s += fmt.Sprintf(",power=%d", c.power) |
1270 | + } |
1271 | + if c.mem > 0 { |
1272 | + s += fmt.Sprintf(",mem=%dG", c.mem/gosigma.Gigabyte) |
1273 | + } |
1274 | + return s |
1275 | +} |
1276 | |
1277 | === added file 'provider/cloudsigma/constraints_test.go' |
1278 | --- provider/cloudsigma/constraints_test.go 1970-01-01 00:00:00 +0000 |
1279 | +++ provider/cloudsigma/constraints_test.go 2014-06-09 11:10:33 +0000 |
1280 | @@ -0,0 +1,136 @@ |
1281 | +// Copyright 2014 Canonical Ltd. |
1282 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1283 | + |
1284 | +package cloudsigma |
1285 | + |
1286 | +import ( |
1287 | + "github.com/Altoros/gosigma" |
1288 | + gc "launchpad.net/gocheck" |
1289 | + "launchpad.net/juju-core/constraints" |
1290 | + "launchpad.net/juju-core/testing" |
1291 | +) |
1292 | + |
1293 | +type constraintsSuite struct { |
1294 | + testing.BaseSuite |
1295 | +} |
1296 | + |
1297 | +var _ = gc.Suite(&constraintsSuite{}) |
1298 | + |
1299 | +type strv struct{ v string } |
1300 | +type uint64v struct{ v uint64 } |
1301 | + |
1302 | +var defConstraints = map[string]sigmaConstraints{ |
1303 | + "bootstrap-trusty": sigmaConstraints{ |
1304 | + driveTemplate: driveUbuntuTrusty64, |
1305 | + driveSize: 5 * gosigma.Gigabyte, |
1306 | + cores: 1, |
1307 | + power: 2000, |
1308 | + mem: 2 * gosigma.Gigabyte, |
1309 | + }, |
1310 | + "trusty": sigmaConstraints{ |
1311 | + driveTemplate: driveUbuntuTrusty64, |
1312 | + driveSize: 0, |
1313 | + cores: 1, |
1314 | + power: 2000, |
1315 | + mem: 2 * gosigma.Gigabyte, |
1316 | + }, |
1317 | + "trusty-c2-p4000": sigmaConstraints{ |
1318 | + driveTemplate: driveUbuntuTrusty64, |
1319 | + driveSize: 0, |
1320 | + cores: 2, |
1321 | + power: 4000, |
1322 | + mem: 2 * gosigma.Gigabyte, |
1323 | + }, |
1324 | +} |
1325 | + |
1326 | +var newConstraintTests = []struct { |
1327 | + bootstrap bool |
1328 | + arch *strv |
1329 | + cores *uint64v |
1330 | + power *uint64v |
1331 | + mem *uint64v |
1332 | + disk *uint64v |
1333 | + series string |
1334 | + expected sigmaConstraints |
1335 | + err *strv |
1336 | +}{ |
1337 | + {true, nil, nil, nil, nil, nil, "trusty", defConstraints["bootstrap-trusty"], nil}, |
1338 | + {false, nil, nil, nil, nil, nil, "trusty", defConstraints["trusty"], nil}, |
1339 | + {true, nil, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}}, |
1340 | + {false, nil, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}}, |
1341 | + {true, &strv{"amd64"}, nil, nil, nil, nil, "trusty", defConstraints["bootstrap-trusty"], nil}, |
1342 | + {false, &strv{"amd64"}, nil, nil, nil, nil, "trusty", defConstraints["trusty"], nil}, |
1343 | + {true, &strv{"amd64"}, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}}, |
1344 | + {false, &strv{"amd64"}, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}}, |
1345 | + {true, nil, &uint64v{1}, nil, nil, nil, "trusty", defConstraints["bootstrap-trusty"], nil}, |
1346 | + {false, nil, &uint64v{1}, nil, nil, nil, "trusty", defConstraints["trusty"], nil}, |
1347 | + {false, nil, &uint64v{2}, nil, nil, nil, "trusty", defConstraints["trusty-c2-p4000"], nil}, |
1348 | + {false, nil, &uint64v{2}, &uint64v{4000}, nil, nil, "trusty", defConstraints["trusty-c2-p4000"], nil}, |
1349 | + {false, nil, nil, nil, &uint64v{2 * 1024}, nil, "trusty", defConstraints["trusty"], nil}, |
1350 | + {false, nil, nil, nil, nil, &uint64v{5 * 1024}, "trusty", defConstraints["bootstrap-trusty"], nil}, |
1351 | +} |
1352 | + |
1353 | +func (s *constraintsSuite) TestConstraints(c *gc.C) { |
1354 | + for i, t := range newConstraintTests { |
1355 | + var cv constraints.Value |
1356 | + if t.arch != nil { |
1357 | + cv.Arch = &t.arch.v |
1358 | + } |
1359 | + if t.cores != nil { |
1360 | + cv.CpuCores = &t.cores.v |
1361 | + } |
1362 | + if t.power != nil { |
1363 | + cv.CpuPower = &t.power.v |
1364 | + } |
1365 | + if t.mem != nil { |
1366 | + cv.Mem = &t.mem.v |
1367 | + } |
1368 | + if t.disk != nil { |
1369 | + cv.RootDisk = &t.disk.v |
1370 | + } |
1371 | + v, err := newConstraints(t.bootstrap, cv, t.series) |
1372 | + if t.err == nil { |
1373 | + if !c.Check(*v, gc.Equals, t.expected) { |
1374 | + c.Logf("test (%d): %+v", i, t) |
1375 | + } |
1376 | + } else { |
1377 | + if !c.Check(err, gc.ErrorMatches, t.err.v) { |
1378 | + c.Logf("test (%d): %+v", i, t) |
1379 | + } |
1380 | + } |
1381 | + } |
1382 | +} |
1383 | + |
1384 | +func (s *constraintsSuite) TestConstraintsArch(c *gc.C) { |
1385 | + var cv constraints.Value |
1386 | + var expected = sigmaConstraints{ |
1387 | + driveTemplate: driveUbuntuTrusty64, |
1388 | + driveSize: 5 * gosigma.Gigabyte, |
1389 | + cores: 1, |
1390 | + power: 2000, |
1391 | + mem: 2 * gosigma.Gigabyte, |
1392 | + } |
1393 | + |
1394 | + sc, err := newConstraints(true, cv, "trusty") |
1395 | + c.Check(err, gc.IsNil) |
1396 | + c.Check(*sc, gc.Equals, expected) |
1397 | + |
1398 | + sc, err = newConstraints(true, cv, "") |
1399 | + c.Check(err, gc.ErrorMatches, "series '.*' not supported") |
1400 | + c.Check(sc, gc.IsNil) |
1401 | + |
1402 | + arch := "amd64" |
1403 | + cv.Arch = &arch |
1404 | + sc, err = newConstraints(true, cv, "trusty") |
1405 | + c.Check(err, gc.IsNil) |
1406 | + c.Check(*sc, gc.Equals, expected) |
1407 | + |
1408 | + sc, err = newConstraints(true, cv, "") |
1409 | + c.Check(err, gc.ErrorMatches, "series '.*' not supported") |
1410 | + c.Check(sc, gc.IsNil) |
1411 | + |
1412 | + arch = "" |
1413 | + sc, err = newConstraints(true, cv, "") |
1414 | + c.Check(err, gc.ErrorMatches, "arch '.*' not supported") |
1415 | + c.Check(sc, gc.IsNil) |
1416 | +} |
1417 | |
1418 | === added file 'provider/cloudsigma/environ.go' |
1419 | --- provider/cloudsigma/environ.go 1970-01-01 00:00:00 +0000 |
1420 | +++ provider/cloudsigma/environ.go 2014-06-09 11:10:33 +0000 |
1421 | @@ -0,0 +1,164 @@ |
1422 | +// Copyright 2014 Canonical Ltd. |
1423 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1424 | + |
1425 | +package cloudsigma |
1426 | + |
1427 | +import ( |
1428 | + "sync" |
1429 | + |
1430 | + "github.com/Altoros/gosigma" |
1431 | + "launchpad.net/juju-core/constraints" |
1432 | + "launchpad.net/juju-core/environs" |
1433 | + "launchpad.net/juju-core/environs/config" |
1434 | + "launchpad.net/juju-core/environs/simplestreams" |
1435 | + "launchpad.net/juju-core/environs/storage" |
1436 | + "launchpad.net/juju-core/environs/tools" |
1437 | + "launchpad.net/juju-core/provider/common" |
1438 | + "launchpad.net/juju-core/state" |
1439 | + "launchpad.net/juju-core/state/api" |
1440 | +) |
1441 | + |
1442 | +// This file contains the core of the Environ implementation. |
1443 | +type environ struct { |
1444 | + name string |
1445 | + |
1446 | + lock sync.Mutex |
1447 | + |
1448 | + ecfg *environConfig |
1449 | + client *environClient |
1450 | + storage *environStorage |
1451 | +} |
1452 | + |
1453 | +var _ environs.Environ = (*environ)(nil) |
1454 | +var _ tools.SupportsCustomSources = (*environ)(nil) |
1455 | +var _ simplestreams.HasRegion = (*environ)(nil) |
1456 | + |
1457 | +// Name returns the Environ's name. |
1458 | +func (env environ) Name() string { |
1459 | + return env.name |
1460 | +} |
1461 | + |
1462 | +// Provider returns the EnvironProvider that created this Environ. |
1463 | +func (environ) Provider() environs.EnvironProvider { |
1464 | + return providerInstance |
1465 | +} |
1466 | + |
1467 | +// SetConfig updates the Environ's configuration. |
1468 | +// |
1469 | +// Calls to SetConfig do not affect the configuration of values previously obtained |
1470 | +// from Storage. |
1471 | +func (env *environ) SetConfig(cfg *config.Config) error { |
1472 | + env.lock.Lock() |
1473 | + defer env.lock.Unlock() |
1474 | + |
1475 | + ecfg, err := validateConfig(cfg, env.ecfg) |
1476 | + if err != nil { |
1477 | + return err |
1478 | + } |
1479 | + |
1480 | + if env.client == nil || env.client.configChanged(ecfg) { |
1481 | + client, err := newClient(ecfg) |
1482 | + if err != nil { |
1483 | + return err |
1484 | + } |
1485 | + |
1486 | + storage, err := newStorage(ecfg, client) |
1487 | + if err != nil { |
1488 | + return err |
1489 | + } |
1490 | + |
1491 | + env.client = client |
1492 | + env.storage = storage |
1493 | + } |
1494 | + |
1495 | + env.ecfg = ecfg |
1496 | + |
1497 | + return nil |
1498 | +} |
1499 | + |
1500 | +// Config returns the configuration data with which the Environ was created. |
1501 | +// Note that this is not necessarily current; the canonical location |
1502 | +// for the configuration data is stored in the state. |
1503 | +func (env environ) Config() *config.Config { |
1504 | + return env.ecfg.Config |
1505 | +} |
1506 | + |
1507 | +// Storage returns storage specific to the environment. |
1508 | +func (env environ) Storage() storage.Storage { |
1509 | + return env.storage |
1510 | +} |
1511 | + |
1512 | +// Bootstrap initializes the state for the environment, possibly |
1513 | +// starting one or more instances. If the configuration's |
1514 | +// AdminSecret is non-empty, the administrator password on the |
1515 | +// newly bootstrapped state will be set to a hash of it (see |
1516 | +// utils.PasswordHash), When first connecting to the |
1517 | +// environment via the juju package, the password hash will be |
1518 | +// automatically replaced by the real password. |
1519 | +// |
1520 | +// The supplied constraints are used to choose the initial instance |
1521 | +// specification, and will be stored in the new environment's state. |
1522 | +// |
1523 | +// Bootstrap is responsible for selecting the appropriate tools, |
1524 | +// and setting the agent-version configuration attribute prior to |
1525 | +// bootstrapping the environment. |
1526 | +func (env *environ) Bootstrap(ctx environs.BootstrapContext, params environs.BootstrapParams) error { |
1527 | + // You can probably ignore this method; the common implementation should work. |
1528 | + return common.Bootstrap(ctx, env, params) |
1529 | +} |
1530 | + |
1531 | +// StateInfo returns information on the state initialized by Bootstrap. |
1532 | +func (env *environ) StateInfo() (*state.Info, *api.Info, error) { |
1533 | + // You can probably ignore this method; the common implementation should work. |
1534 | + return common.StateInfo(env) |
1535 | +} |
1536 | + |
1537 | +// Destroy shuts down all known machines and destroys the |
1538 | +// rest of the environment. Note that on some providers, |
1539 | +// very recently started instances may not be destroyed |
1540 | +// because they are not yet visible. |
1541 | +// |
1542 | +// When Destroy has been called, any Environ referring to the |
1543 | +// same remote environment may become invalid |
1544 | +func (env *environ) Destroy() error { |
1545 | + // You can probably ignore this method; the common implementation should work. |
1546 | + return common.Destroy(env) |
1547 | +} |
1548 | + |
1549 | +// PrecheckInstance performs a preflight check on the specified |
1550 | +// series and constraints, ensuring that they are possibly valid for |
1551 | +// creating an instance in this environment. |
1552 | +// |
1553 | +// PrecheckInstance is best effort, and not guaranteed to eliminate |
1554 | +// all invalid parameters. If PrecheckInstance returns nil, it is not |
1555 | +// guaranteed that the constraints are valid; if a non-nil error is |
1556 | +// returned, then the constraints are definitely invalid. |
1557 | +func (env environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
1558 | + logger.Infof("cloudsigma:environ:PrecheckInstance") |
1559 | + return nil |
1560 | +} |
1561 | + |
1562 | +// GetToolsSources returns a list of sources which are |
1563 | +// used to search for simplestreams tools metadata. |
1564 | +func (env environ) GetToolsSources() ([]simplestreams.DataSource, error) { |
1565 | + // Add the simplestreams source off private storage. |
1566 | + return []simplestreams.DataSource{ |
1567 | + storage.NewStorageSimpleStreamsDataSource("cloud storage", env.Storage(), storage.BaseToolsPath), |
1568 | + }, nil |
1569 | +} |
1570 | + |
1571 | +// Region is specified in the HasRegion interface. |
1572 | +func (env environ) Region() (simplestreams.CloudSpec, error) { |
1573 | + return env.cloudSpec(env.ecfg.region()) |
1574 | +} |
1575 | + |
1576 | +func (env environ) cloudSpec(region string) (simplestreams.CloudSpec, error) { |
1577 | + endpoint, err := gosigma.ResolveEndpoint(region) |
1578 | + if err != nil { |
1579 | + return simplestreams.CloudSpec{}, err |
1580 | + } |
1581 | + return simplestreams.CloudSpec{ |
1582 | + Region: region, |
1583 | + Endpoint: endpoint, |
1584 | + }, nil |
1585 | +} |
1586 | |
1587 | === added file 'provider/cloudsigma/environ_test.go' |
1588 | --- provider/cloudsigma/environ_test.go 1970-01-01 00:00:00 +0000 |
1589 | +++ provider/cloudsigma/environ_test.go 2014-06-09 11:10:33 +0000 |
1590 | @@ -0,0 +1,96 @@ |
1591 | +// Copyright 2014 Canonical Ltd. |
1592 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1593 | + |
1594 | +package cloudsigma |
1595 | + |
1596 | +import ( |
1597 | + gc "launchpad.net/gocheck" |
1598 | + "launchpad.net/juju-core/constraints" |
1599 | + "launchpad.net/juju-core/environs" |
1600 | + "launchpad.net/juju-core/environs/simplestreams" |
1601 | + "launchpad.net/juju-core/environs/tools" |
1602 | + "launchpad.net/juju-core/juju/arch" |
1603 | + "launchpad.net/juju-core/testing" |
1604 | +) |
1605 | + |
1606 | +type environSuite struct { |
1607 | + testing.BaseSuite |
1608 | +} |
1609 | + |
1610 | +var _ = gc.Suite(&environSuite{}) |
1611 | + |
1612 | +func (s *environSuite) SetUpSuite(c *gc.C) { |
1613 | + s.BaseSuite.SetUpSuite(c) |
1614 | +} |
1615 | + |
1616 | +func (s *environSuite) TearDownSuite(c *gc.C) { |
1617 | + s.BaseSuite.TearDownSuite(c) |
1618 | +} |
1619 | + |
1620 | +func (s *environSuite) SetUpTest(c *gc.C) { |
1621 | + s.BaseSuite.SetUpTest(c) |
1622 | +} |
1623 | + |
1624 | +func (s *environSuite) TearDownTest(c *gc.C) { |
1625 | + s.BaseSuite.TearDownTest(c) |
1626 | +} |
1627 | + |
1628 | +func (s *environSuite) TestBase(c *gc.C) { |
1629 | + var emptyStorage environStorage |
1630 | + |
1631 | + s.PatchValue(&newClient, func(*environConfig) (*environClient, error) { |
1632 | + return nil, nil |
1633 | + }) |
1634 | + s.PatchValue(&newStorage, func(*environConfig, *environClient) (*environStorage, error) { |
1635 | + return &emptyStorage, nil |
1636 | + }) |
1637 | + |
1638 | + baseConfig := newConfig(c, validAttrs().Merge(testing.Attrs{"name": "testname"})) |
1639 | + environ, err := environs.New(baseConfig) |
1640 | + c.Assert(err, gc.IsNil) |
1641 | + |
1642 | + cfg := environ.Config() |
1643 | + c.Assert(cfg, gc.NotNil) |
1644 | + c.Check(cfg.Name(), gc.Equals, "testname") |
1645 | + |
1646 | + c.Check(environ.Storage(), gc.Equals, &emptyStorage) |
1647 | + |
1648 | + c.Check(environ.PrecheckInstance("", constraints.Value{}, ""), gc.IsNil) |
1649 | + |
1650 | + customSource, ok := environ.(tools.SupportsCustomSources) |
1651 | + c.Check(ok, gc.Equals, true) |
1652 | + c.Assert(customSource, gc.NotNil) |
1653 | + |
1654 | + src, err := customSource.GetToolsSources() |
1655 | + c.Check(src, gc.NotNil) |
1656 | + c.Check(err, gc.IsNil) |
1657 | + |
1658 | + hasRegion, ok := environ.(simplestreams.HasRegion) |
1659 | + c.Check(ok, gc.Equals, true) |
1660 | + c.Assert(hasRegion, gc.NotNil) |
1661 | + |
1662 | + cloudSpec, err := hasRegion.Region() |
1663 | + c.Check(err, gc.IsNil) |
1664 | + c.Check(cloudSpec.Region, gc.Not(gc.Equals), "") |
1665 | + c.Check(cloudSpec.Endpoint, gc.Not(gc.Equals), "") |
1666 | + |
1667 | + archs, err := environ.SupportedArchitectures() |
1668 | + c.Check(err, gc.IsNil) |
1669 | + c.Assert(archs, gc.NotNil) |
1670 | + c.Assert(archs, gc.HasLen, 1) |
1671 | + c.Check(archs[0], gc.Equals, arch.AMD64) |
1672 | + |
1673 | + validator, err := environ.ConstraintsValidator() |
1674 | + c.Check(validator, gc.NotNil) |
1675 | + c.Check(err, gc.IsNil) |
1676 | + |
1677 | + c.Check(environ.SupportNetworks(), gc.Equals, false) |
1678 | + c.Check(environ.SupportsUnitPlacement(), gc.IsNil) |
1679 | + |
1680 | + c.Check(environ.OpenPorts(nil), gc.IsNil) |
1681 | + c.Check(environ.ClosePorts(nil), gc.IsNil) |
1682 | + |
1683 | + ports, err := environ.Ports() |
1684 | + c.Check(ports, gc.IsNil) |
1685 | + c.Check(err, gc.IsNil) |
1686 | +} |
1687 | |
1688 | === added file 'provider/cloudsigma/environcaps.go' |
1689 | --- provider/cloudsigma/environcaps.go 1970-01-01 00:00:00 +0000 |
1690 | +++ provider/cloudsigma/environcaps.go 2014-06-09 11:10:33 +0000 |
1691 | @@ -0,0 +1,50 @@ |
1692 | +// Copyright 2014 Canonical Ltd. |
1693 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1694 | + |
1695 | +package cloudsigma |
1696 | + |
1697 | +import ( |
1698 | + "launchpad.net/juju-core/constraints" |
1699 | + "launchpad.net/juju-core/juju/arch" |
1700 | +) |
1701 | + |
1702 | +// SupportedArchitectures returns the image architectures which can |
1703 | +// be hosted by this environment. |
1704 | +func (env *environ) SupportedArchitectures() ([]string, error) { |
1705 | + return []string{arch.AMD64}, nil |
1706 | +} |
1707 | + |
1708 | +var unsupportedConstraints = []string{ |
1709 | + constraints.Container, |
1710 | + constraints.InstanceType, |
1711 | + constraints.Tags, |
1712 | +} |
1713 | + |
1714 | +// ConstraintsValidator returns a Validator instance which |
1715 | +// is used to validate and merge constraints. |
1716 | +func (env *environ) ConstraintsValidator() (constraints.Validator, error) { |
1717 | + validator := constraints.NewValidator() |
1718 | + validator.RegisterUnsupported(unsupportedConstraints) |
1719 | + supportedArches, err := env.SupportedArchitectures() |
1720 | + if err != nil { |
1721 | + return nil, err |
1722 | + } |
1723 | + validator.RegisterVocabulary(constraints.Arch, supportedArches) |
1724 | + return validator, nil |
1725 | +} |
1726 | + |
1727 | +// SupportNetworks returns whether the environment has support to |
1728 | +// specify networks for services and machines. |
1729 | +func (env *environ) SupportNetworks() bool { |
1730 | + logger.Debugf("environ:SupportedNetworks") |
1731 | + return false |
1732 | +} |
1733 | + |
1734 | +// SupportsUnitAssignment returns an error which, if non-nil, indicates |
1735 | +// that the environment does not support unit placement. If the environment |
1736 | +// does not support unit placement, then machines may not be created |
1737 | +// without units, and units cannot be placed explcitly. |
1738 | +func (env *environ) SupportsUnitPlacement() error { |
1739 | + logger.Debugf("cloudsigma:environ:SupportsUnitPlacement not implemented") |
1740 | + return nil |
1741 | +} |
1742 | |
1743 | === added file 'provider/cloudsigma/environfirewall.go' |
1744 | --- provider/cloudsigma/environfirewall.go 1970-01-01 00:00:00 +0000 |
1745 | +++ provider/cloudsigma/environfirewall.go 2014-06-09 11:10:33 +0000 |
1746 | @@ -0,0 +1,32 @@ |
1747 | +// Copyright 2014 Canonical Ltd. |
1748 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1749 | + |
1750 | +package cloudsigma |
1751 | + |
1752 | +import ( |
1753 | + "launchpad.net/juju-core/instance" |
1754 | +) |
1755 | + |
1756 | +// Implementing the methods below (to do something other than return nil) will |
1757 | +// cause `juju expose` to work when the firewall-mode is "global". If you |
1758 | +// implement one of them, you should implement them all. |
1759 | + |
1760 | +// OpenPorts opens the given ports for the whole environment. |
1761 | +// Must only be used if the environment was setup with the FwGlobal firewall mode. |
1762 | +func (env environ) OpenPorts(ports []instance.Port) error { |
1763 | + logger.Warningf("pretending to open ports %v for all instances", ports) |
1764 | + return nil |
1765 | +} |
1766 | + |
1767 | +// ClosePorts closes the given ports for the whole environment. |
1768 | +// Must only be used if the environment was setup with the FwGlobal firewall mode. |
1769 | +func (env environ) ClosePorts(ports []instance.Port) error { |
1770 | + logger.Warningf("pretending to close ports %v for all instances", ports) |
1771 | + return nil |
1772 | +} |
1773 | + |
1774 | +// Ports returns the ports opened for the whole environment. |
1775 | +// Must only be used if the environment was setup with the FwGlobal firewall mode. |
1776 | +func (env environ) Ports() ([]instance.Port, error) { |
1777 | + return nil, nil |
1778 | +} |
1779 | |
1780 | === added file 'provider/cloudsigma/environinstance.go' |
1781 | --- provider/cloudsigma/environinstance.go 1970-01-01 00:00:00 +0000 |
1782 | +++ provider/cloudsigma/environinstance.go 2014-06-09 11:10:33 +0000 |
1783 | @@ -0,0 +1,291 @@ |
1784 | +// Copyright 2014 Canonical Ltd. |
1785 | +// Licensed under the AGPLv3, see LICENCE file for details. |
1786 | + |
1787 | +package cloudsigma |
1788 | + |
1789 | +import ( |
1790 | + "fmt" |
1791 | + "os" |
1792 | + "path" |
1793 | + "strings" |
1794 | + "time" |
1795 | + |
1796 | + "github.com/Altoros/gosigma" |
1797 | + "github.com/juju/errors" |
1798 | + "github.com/juju/loggo" |
1799 | + "launchpad.net/juju-core/agent" |
1800 | + coreCloudinit "launchpad.net/juju-core/cloudinit" |
1801 | + "launchpad.net/juju-core/cloudinit/sshinit" |
1802 | + "launchpad.net/juju-core/environs" |
1803 | + "launchpad.net/juju-core/environs/cloudinit" |
1804 | + "launchpad.net/juju-core/environs/network" |
1805 | + "launchpad.net/juju-core/instance" |
1806 | + "launchpad.net/juju-core/juju/arch" |
1807 | + "launchpad.net/juju-core/utils" |
1808 | + "launchpad.net/juju-core/utils/ssh" |
1809 | + "launchpad.net/juju-core/worker/localstorage" |
1810 | +) |
1811 | + |
1812 | +// |
1813 | +// Imlementation of InstanceBroker: methods for starting and stopping instances. |
1814 | +// |
1815 | + |
1816 | +// StartInstance asks for a new instance to be created, associated with |
1817 | +// the provided config in machineConfig. The given config describes the juju |
1818 | +// state for the new instance to connect to. The config MachineNonce, which must be |
1819 | +// unique within an environment, is used by juju to protect against the |
1820 | +// consequences of multiple instances being started with the same machine id. |
1821 | +func (env environ) StartInstance(args environs.StartInstanceParams) ( |
1822 | + instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) { |
1823 | + logger.Infof("sigmaEnviron.StartInstance...") |
1824 | + |
1825 | + if args.MachineConfig == nil { |
1826 | + return nil, nil, nil, fmt.Errorf("machine configuration is nil") |
1827 | + } |
1828 | + |
1829 | + if args.MachineConfig.HasNetworks() { |
1830 | + return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet") |
1831 | + } |
1832 | + |
1833 | + if len(args.Tools) == 0 { |
1834 | + return nil, nil, nil, fmt.Errorf("tools not found") |
1835 | + } |
1836 | + |
1837 | + args.MachineConfig.Tools = args.Tools[0] |
1838 | + if err := environs.FinishMachineConfig(args.MachineConfig, env.Config(), args.Constraints); err != nil { |
1839 | + return nil, nil, nil, err |
1840 | + } |
1841 | + |
1842 | + client := env.client |
1843 | + server, rootdrive, err := client.newInstance(args) |
1844 | + if err != nil { |
1845 | + return nil, nil, nil, fmt.Errorf("failed start instance: %v", err) |
1846 | + } |
1847 | + |
1848 | + inst := sigmaInstance{server: server} |
1849 | + addr := inst.findIPv4() |
1850 | + if addr == "" { |
1851 | + return nil, nil, nil, fmt.Errorf("failed obtain instance IP address") |
1852 | + } |
1853 | + |
1854 | + // prepare new instance: wait up and running, populate nonce file, etc |
1855 | + if err := env.prepareInstance(addr, args.MachineConfig); err != nil { |
1856 | + return nil, nil, nil, fmt.Errorf("failed prepare instanse: %v", err) |
1857 | + } |
1858 | + |
1859 | + // provide additional agent config for localstorage, if any |
1860 | + if env.storage.tmp && args.MachineConfig.Bootstrap { |
1861 | + if err := env.prepareStorage(addr, args.MachineConfig); err != nil { |
1862 | + return nil, nil, nil, fmt.Errorf("failed prepare storage: %v", err) |
1863 | + } |
1864 | + } |
1865 | + |
1866 | + // prepare hardware characteristics |
1867 | + hwch := inst.hardware() |
1868 | + |
1869 | + // populate root drive hardware characteristics |
1870 | + switch rootdrive.Arch() { |
1871 | + case "64": |
1872 | + var a = arch.AMD64 |
1873 | + hwch.Arch = &a |
1874 | + } |
1875 | + |
1876 | + diskSpace := rootdrive.Size() / gosigma.Megabyte |
1877 | + if diskSpace > 0 { |
1878 | + hwch.RootDisk = &diskSpace |
1879 | + } |
1880 | + |
1881 | + logger.Tracef("hardware: %v", hwch) |
1882 | + |
1883 | + return inst, hwch, nil, nil |
1884 | +} |
1885 | + |
1886 | +// AllInstances returns all instances currently known to the broker. |
1887 | +func (env environ) AllInstances() ([]instance.Instance, error) { |
1888 | + // Please note that this must *not* return instances that have not been |
1889 | + // allocated as part of this environment -- if it does, juju will see they |
1890 | + // are not tracked in state, assume they're stale/rogue, and shut them down. |
1891 | + |
1892 | + logger.Tracef("environ.AllInstances...") |
1893 | + |
1894 | + servers, err := env.client.instances() |
1895 | + if err != nil { |
1896 | + logger.Tracef("environ.AllInstances failed: %v", err) |
1897 | + return nil, err |
1898 | + } |
1899 | + |
1900 | + instances := make([]instance.Instance, 0, len(servers)) |
1901 | + for _, server := range servers { |
1902 | + instance := sigmaInstance{server: server} |
1903 | + instances = append(instances, instance) |
1904 | + } |
1905 | + |
1906 | + if logger.LogLevel() <= loggo.TRACE { |
1907 | + logger.Tracef("All instances, len = %d:", len(instances)) |
1908 | + for _, instance := range instances { |
1909 | + logger.Tracef("... id: %q, status: %q", instance.Id(), instance.Status()) |
1910 | + } |
1911 | + } |
1912 | + |
1913 | + return instances, nil |
1914 | +} |
1915 | + |
1916 | +// Instances returns a slice of instances corresponding to the |
1917 | +// given instance ids. If no instances were found, but there |
1918 | +// was no other error, it will return ErrNoInstances. If |
1919 | +// some but not all the instances were found, the returned slice |
1920 | +// will have some nil slots, and an ErrPartialInstances error |
1921 | +// will be returned. |
1922 | +func (env environ) Instances(ids []instance.Id) ([]instance.Instance, error) { |
1923 | + logger.Tracef("environ.Instances %#v", ids) |
1924 | + // Please note that this must *not* return instances that have not been |
1925 | + // allocated as part of this environment -- if it does, juju will see they |
1926 | + // are not tracked in state, assume they're stale/rogue, and shut them down. |
1927 | + // This advice applies even if an instance id passed in corresponds to a |
1928 | + // real instance that's not part of the environment -- the Environ should |
1929 | + // treat that no differently to a request for one that does not exist. |
1930 | + |
1931 | + m, err := env.client.instanceMap() |
1932 | + if err != nil { |
1933 | + logger.Tracef("environ.Instances failed: %v", err) |
1934 | + return nil, err |
1935 | + } |
1936 | + |
1937 | + var found int |
1938 | + r := make([]instance.Instance, len(ids)) |
1939 | + for i, id := range ids { |
1940 | + if s, ok := m[string(id)]; ok { |
1941 | + r[i] = sigmaInstance{server: s} |
1942 | + found++ |
1943 | + } |
1944 | + } |
1945 | + |
1946 | + if found == 0 { |
1947 | + err = environs.ErrNoInstances |
1948 | + } else if found != len(ids) { |
1949 | + err = environs.ErrPartialInstances |
1950 | + } |
1951 | + |
1952 | + return r, err |
1953 | +} |
1954 | + |
1955 | +// StopInstances shuts down the given instances. |
1956 | +func (env environ) StopInstances(instances ...instance.Id) error { |
1957 | + logger.Infof("stop instances %+v", instances) |
1958 | + |
1959 | + var err error |
1960 | + |
1961 | + for _, instance := range instances { |
1962 | + if e := env.client.stopInstance(instance); e != nil { |
1963 | + err = e |
1964 | + } |
1965 | + } |
1966 | + |
1967 | + return err |
1968 | +} |
1969 | + |
1970 | +func (env environ) prepareInstance(addr string, mcfg *cloudinit.MachineConfig) error { |
1971 | + host := "ubuntu@" + addr |
1972 | + logger.Debugf("Running prepare script on %s", host) |
1973 | + |
1974 | + cmds := []string{"#!/bin/bash", "set -e"} |
1975 | + cmds = append(cmds, fmt.Sprintf("mkdir -p '%s'", mcfg.DataDir)) |
1976 | + |
1977 | + nonceFile := path.Join(mcfg.DataDir, cloudinit.NonceFile) |
1978 | + nonceContent := mcfg.MachineNonce |
1979 | + cmds = append(cmds, fmt.Sprintf("echo '%s' > %s", nonceContent, nonceFile)) |
1980 | + |
1981 | + script := strings.Join(cmds, "\n") |
1982 | + |
1983 | + if !mcfg.Bootstrap { |
1984 | + cloudcfg := coreCloudinit.New() |
1985 | + if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil { |
1986 | + return err |
1987 | + } |
1988 | + configScript, err := sshinit.ConfigureScript(cloudcfg) |
1989 | + if err != nil { |
1990 | + return err |
1991 | + } |
1992 | + |
1993 | + script += "\n\n" |
1994 | + script += configScript |
1995 | + } |
1996 | + |
1997 | + logger.Tracef("Script:\n%s", script) |
1998 | + |
1999 | + keyfile := path.Join(mcfg.DataDir, agent.SystemIdentity) |
2000 | + if _, err := os.Stat(keyfile); err != nil { |
2001 | + keyfile = "" |
2002 | + } |
2003 | + logger.Tracef("System identity file: %q", keyfile) |
2004 | + |
2005 | + var retryAttempts = utils.AttemptStrategy{ |
2006 | + Total: 120 * time.Second, |
2007 | + Delay: 300 * time.Millisecond, |
2008 | + } |
2009 | + |
2010 | + var err error |
2011 | + for attempt := retryAttempts.Start(); attempt.Next(); { |
2012 | + var options ssh.Options |
2013 | + if keyfile != "" { |
2014 | + options.SetIdentities(keyfile) |
2015 | + } |
2016 | + |
2017 | + cmd := ssh.Command(host, []string{"sudo", "/bin/bash"}, &options) |
2018 | + cmd.Stdin = strings.NewReader(script) |
2019 | + |
2020 | + var logw = loggerWriter{logger.LogLevel()} |
2021 | + cmd.Stderr = &logw |
2022 | + cmd.Stdout = &logw |
2023 | + |
2024 | + if err = cmd.Run(); err == nil { |
2025 | + break |
2026 | + } |
2027 | + } |
2028 | + |
2029 | + if err != nil { |
2030 | + return fmt.Errorf("failed running prepare script: %v", err) |
2031 | + } |
2032 | + |
2033 | + return nil |
2034 | +} |
2035 | + |
2036 | +func (env environ) prepareStorage(addr string, mcfg *cloudinit.MachineConfig) error { |
2037 | + storagePort := env.ecfg.storagePort() |
2038 | + storageDir := mcfg.DataDir + "/" + storageSubdir |
2039 | + |
2040 | + logger.Debugf("Moving local temporary storage to %s:%d (%s)...", addr, storagePort, storageDir) |
2041 | + if err := env.storage.MoveToSSH("ubuntu", addr); err != nil { |
2042 | + return err |
2043 | + } |
2044 | + |
2045 | + if strings.Contains(mcfg.Tools.URL, "%s") { |
2046 | + mcfg.Tools.URL = fmt.Sprintf(mcfg.Tools.URL, "file://"+storageDir) |
2047 | + logger.Tracef("Tools URL patched to %q", mcfg.Tools.URL) |
2048 | + } |
2049 | + |
2050 | + // prepare configuration for local storage at bootstrap host |
2051 | + storageConfig := storageConfig{ |
2052 | + ecfg: env.ecfg, |
2053 | + storageDir: storageDir, |
2054 | + storageAddr: addr, |
2055 | + storagePort: storagePort, |
2056 | + } |
2057 | + |
2058 | + agentEnv, err := localstorage.StoreConfig(&storageConfig) |
2059 | + if err != nil { |
2060 | + return err |
2061 | + } |
2062 | + |
2063 | + for k, v := range agentEnv { |
2064 | + mcfg.AgentEnvironment[k] = v |
2065 | + } |
2066 | + |
2067 | + return nil |
2068 | +} |
2069 | + |
2070 | +// AllocateAddress requests a new address to be allocated for the |
2071 | +// given instance on the given network. |
2072 | +func (env environ) AllocateAddress(instID instance.Id, netID network.Id) (instance.Address, error) { |
2073 | + return instance.Address{}, errors.NotSupportedf("AllocateAddress") |
2074 | +} |
2075 | |
2076 | === added file 'provider/cloudsigma/environinstance_test.go' |
2077 | --- provider/cloudsigma/environinstance_test.go 1970-01-01 00:00:00 +0000 |
2078 | +++ provider/cloudsigma/environinstance_test.go 2014-06-09 11:10:33 +0000 |
2079 | @@ -0,0 +1,206 @@ |
2080 | +// Copyright 2014 Canonical Ltd. |
2081 | +// Licensed under the AGPLv3, see LICENCE file for details. |
2082 | + |
2083 | +package cloudsigma |
2084 | + |
2085 | +import ( |
2086 | + "time" |
2087 | + |
2088 | + "github.com/Altoros/gosigma/mock" |
2089 | + "github.com/juju/loggo" |
2090 | + gc "launchpad.net/gocheck" |
2091 | + "launchpad.net/juju-core/environs" |
2092 | + "launchpad.net/juju-core/environs/cloudinit" |
2093 | + "launchpad.net/juju-core/environs/config" |
2094 | + "launchpad.net/juju-core/environs/network" |
2095 | + "launchpad.net/juju-core/instance" |
2096 | + "launchpad.net/juju-core/state/api" |
2097 | + "launchpad.net/juju-core/testing" |
2098 | + "launchpad.net/juju-core/tools" |
2099 | +) |
2100 | + |
2101 | +type environInstanceSuite struct { |
2102 | + testing.BaseSuite |
2103 | + baseConfig *config.Config |
2104 | +} |
2105 | + |
2106 | +var _ = gc.Suite(&environInstanceSuite{}) |
2107 | + |
2108 | +func (s *environInstanceSuite) SetUpSuite(c *gc.C) { |
2109 | + s.BaseSuite.SetUpSuite(c) |
2110 | + |
2111 | + mock.Start() |
2112 | + |
2113 | + attrs := testing.Attrs{ |
2114 | + "name": "testname", |
2115 | + "region": mock.Endpoint(""), |
2116 | + "username": mock.TestUser, |
2117 | + "password": mock.TestPassword, |
2118 | + } |
2119 | + s.baseConfig = newConfig(c, validAttrs().Merge(attrs)) |
2120 | +} |
2121 | + |
2122 | +func (s *environInstanceSuite) TearDownSuite(c *gc.C) { |
2123 | + mock.Stop() |
2124 | + s.BaseSuite.TearDownSuite(c) |
2125 | +} |
2126 | + |
2127 | +func (s *environInstanceSuite) SetUpTest(c *gc.C) { |
2128 | + s.BaseSuite.SetUpTest(c) |
2129 | + |
2130 | + ll := logger.LogLevel() |
2131 | + logger.SetLogLevel(loggo.TRACE) |
2132 | + s.AddCleanup(func(*gc.C) { logger.SetLogLevel(ll) }) |
2133 | + |
2134 | + mock.Reset() |
2135 | +} |
2136 | + |
2137 | +func (s *environInstanceSuite) TearDownTest(c *gc.C) { |
2138 | + mock.Reset() |
2139 | + s.BaseSuite.TearDownTest(c) |
2140 | +} |
2141 | + |
2142 | +func (s *environInstanceSuite) createEnviron(c *gc.C, cfg *config.Config) environs.Environ { |
2143 | + var emptyStorage environStorage |
2144 | + s.PatchValue(&newStorage, func(*environConfig, *environClient) (*environStorage, error) { |
2145 | + return &emptyStorage, nil |
2146 | + }) |
2147 | + if cfg == nil { |
2148 | + cfg = s.baseConfig |
2149 | + } |
2150 | + environ, err := environs.New(cfg) |
2151 | + c.Assert(environ, gc.NotNil) |
2152 | + c.Assert(err, gc.IsNil) |
2153 | + return environ |
2154 | +} |
2155 | + |
2156 | +func (s *environInstanceSuite) TestInstances(c *gc.C) { |
2157 | + environ := s.createEnviron(c, nil) |
2158 | + |
2159 | + instances, err := environ.AllInstances() |
2160 | + c.Assert(instances, gc.NotNil) |
2161 | + c.Assert(err, gc.IsNil) |
2162 | + c.Check(instances, gc.HasLen, 0) |
2163 | + |
2164 | + uuid0 := addTestClientServer(c, jujuMetaInstanceServer, "testname", "1.1.1.1") |
2165 | + uuid1 := addTestClientServer(c, jujuMetaInstanceStateServer, "testname", "2.2.2.2") |
2166 | + addTestClientServer(c, jujuMetaInstanceServer, "other-env", "0.1.1.1") |
2167 | + addTestClientServer(c, jujuMetaInstanceStateServer, "other-env", "0.2.2.2") |
2168 | + |
2169 | + instances, err = environ.AllInstances() |
2170 | + c.Assert(instances, gc.NotNil) |
2171 | + c.Assert(err, gc.IsNil) |
2172 | + c.Check(instances, gc.HasLen, 2) |
2173 | + |
2174 | + ids := []instance.Id{instance.Id(uuid0), instance.Id(uuid1)} |
2175 | + instances, err = environ.Instances(ids) |
2176 | + c.Assert(instances, gc.NotNil) |
2177 | + c.Assert(err, gc.IsNil) |
2178 | + c.Check(instances, gc.HasLen, 2) |
2179 | + |
2180 | + ids = append(ids, instance.Id("fake-instance")) |
2181 | + instances, err = environ.Instances(ids) |
2182 | + c.Assert(instances, gc.NotNil) |
2183 | + c.Assert(err, gc.Equals, environs.ErrPartialInstances) |
2184 | + c.Check(instances, gc.HasLen, 3) |
2185 | + c.Check(instances[0], gc.NotNil) |
2186 | + c.Check(instances[1], gc.NotNil) |
2187 | + c.Check(instances[2], gc.IsNil) |
2188 | + |
2189 | + err = environ.StopInstances(ids...) |
2190 | + c.Assert(err, gc.ErrorMatches, "404 Not Found.*") |
2191 | + |
2192 | + instances, err = environ.Instances(ids) |
2193 | + c.Assert(instances, gc.NotNil) |
2194 | + c.Assert(err, gc.Equals, environs.ErrNoInstances) |
2195 | + c.Check(instances, gc.HasLen, 3) |
2196 | + c.Check(instances[0], gc.IsNil) |
2197 | + c.Check(instances[1], gc.IsNil) |
2198 | + c.Check(instances[2], gc.IsNil) |
2199 | +} |
2200 | + |
2201 | +func (s *environInstanceSuite) TestInstancesFail(c *gc.C) { |
2202 | + attrs := testing.Attrs{ |
2203 | + "name": "testname", |
2204 | + "region": "https://0.1.2.3:2000/api/2.0/", |
2205 | + "username": mock.TestUser, |
2206 | + "password": mock.TestPassword, |
2207 | + } |
2208 | + baseConfig := newConfig(c, validAttrs().Merge(attrs)) |
2209 | + |
2210 | + newClientFunc := newClient |
2211 | + s.PatchValue(&newClient, func(cfg *environConfig) (*environClient, error) { |
2212 | + cli, err := newClientFunc(cfg) |
2213 | + if cli != nil { |
2214 | + cli.conn.ConnectTimeout(10 * time.Millisecond) |
2215 | + } |
2216 | + return cli, err |
2217 | + }) |
2218 | + |
2219 | + environ := s.createEnviron(c, baseConfig) |
2220 | + |
2221 | + instances, err := environ.AllInstances() |
2222 | + c.Assert(instances, gc.IsNil) |
2223 | + c.Assert(err, gc.NotNil) |
2224 | + |
2225 | + instances, err = environ.Instances([]instance.Id{instance.Id("123"), instance.Id("321")}) |
2226 | + c.Assert(instances, gc.IsNil) |
2227 | + c.Assert(err, gc.NotNil) |
2228 | +} |
2229 | + |
2230 | +func (s *environInstanceSuite) TestAllocateAddress(c *gc.C) { |
2231 | + environ := s.createEnviron(c, nil) |
2232 | + |
2233 | + addr, err := environ.AllocateAddress(instance.Id(""), network.Id("")) |
2234 | + c.Check(addr, gc.Equals, instance.Address{}) |
2235 | + c.Check(err, gc.ErrorMatches, "AllocateAddress not supported") |
2236 | +} |
2237 | + |
2238 | +func (s *environInstanceSuite) TestStartInstanceError(c *gc.C) { |
2239 | + environ := s.createEnviron(c, nil) |
2240 | + |
2241 | + inst, hw, ni, err := environ.StartInstance(environs.StartInstanceParams{}) |
2242 | + c.Check(inst, gc.IsNil) |
2243 | + c.Check(hw, gc.IsNil) |
2244 | + c.Check(ni, gc.IsNil) |
2245 | + c.Check(err, gc.ErrorMatches, "machine configuration is nil") |
2246 | + |
2247 | + inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{ |
2248 | + MachineConfig: &cloudinit.MachineConfig{ |
2249 | + IncludeNetworks: []string{"value"}, |
2250 | + }, |
2251 | + }) |
2252 | + c.Check(inst, gc.IsNil) |
2253 | + c.Check(hw, gc.IsNil) |
2254 | + c.Check(ni, gc.IsNil) |
2255 | + c.Check(err, gc.ErrorMatches, "starting instances with networks is not supported yet") |
2256 | + |
2257 | + inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{ |
2258 | + MachineConfig: &cloudinit.MachineConfig{}, |
2259 | + }) |
2260 | + c.Check(inst, gc.IsNil) |
2261 | + c.Check(hw, gc.IsNil) |
2262 | + c.Check(ni, gc.IsNil) |
2263 | + c.Check(err, gc.ErrorMatches, "tools not found") |
2264 | + |
2265 | + inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{ |
2266 | + Tools: tools.List{&tools.Tools{}}, |
2267 | + MachineConfig: &cloudinit.MachineConfig{ |
2268 | + Bootstrap: true, |
2269 | + APIInfo: &api.Info{}, |
2270 | + }, |
2271 | + }) |
2272 | + c.Check(inst, gc.IsNil) |
2273 | + c.Check(hw, gc.IsNil) |
2274 | + c.Check(ni, gc.IsNil) |
2275 | + c.Check(err, gc.ErrorMatches, "cannot complete machine configuration:.*") |
2276 | + |
2277 | + inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{ |
2278 | + Tools: tools.List{&tools.Tools{}}, |
2279 | + MachineConfig: &cloudinit.MachineConfig{}, |
2280 | + }) |
2281 | + c.Check(inst, gc.IsNil) |
2282 | + c.Check(hw, gc.IsNil) |
2283 | + c.Check(ni, gc.IsNil) |
2284 | + c.Check(err, gc.ErrorMatches, "failed start instance:.*") |
2285 | +} |
2286 | |
2287 | === added file 'provider/cloudsigma/instance.go' |
2288 | --- provider/cloudsigma/instance.go 1970-01-01 00:00:00 +0000 |
2289 | +++ provider/cloudsigma/instance.go 2014-06-09 11:10:33 +0000 |
2290 | @@ -0,0 +1,148 @@ |
2291 | +// Copyright 2014 Canonical Ltd. |
2292 | +// Licensed under the AGPLv3, see LICENCE file for details. |
2293 | + |
2294 | +package cloudsigma |
2295 | + |
2296 | +import ( |
2297 | + "fmt" |
2298 | + |
2299 | + "github.com/Altoros/gosigma" |
2300 | + "launchpad.net/juju-core/instance" |
2301 | + "launchpad.net/juju-core/provider/common" |
2302 | +) |
2303 | + |
2304 | +type sigmaInstance struct { |
2305 | + server gosigma.Server |
2306 | +} |
2307 | + |
2308 | +// Id returns a provider-generated identifier for the Instance. |
2309 | +func (i sigmaInstance) Id() instance.Id { |
2310 | + if i.server == nil { |
2311 | + return instance.Id("") |
2312 | + } |
2313 | + id := instance.Id(i.server.UUID()) |
2314 | + logger.Tracef("sigmaInstance.Id: %s", id) |
2315 | + return id |
2316 | +} |
2317 | + |
2318 | +// Status returns the provider-specific status for the instance. |
2319 | +func (i sigmaInstance) Status() string { |
2320 | + if i.server == nil { |
2321 | + return "" |
2322 | + } |
2323 | + status := i.server.Status() |
2324 | + logger.Tracef("sigmaInstance.Status: %s", status) |
2325 | + return status |
2326 | +} |
2327 | + |
2328 | +// Refresh refreshes local knowledge of the instance from the provider. |
2329 | +func (i sigmaInstance) Refresh() error { |
2330 | + if i.server == nil { |
2331 | + return fmt.Errorf("invalid instance") |
2332 | + } |
2333 | + err := i.server.Refresh() |
2334 | + logger.Tracef("sigmaInstance.Refresh: %s", err) |
2335 | + return err |
2336 | +} |
2337 | + |
2338 | +// Addresses returns a list of hostnames or ip addresses |
2339 | +// associated with the instance. This will supercede DNSName |
2340 | +// which can be implemented by selecting a preferred address. |
2341 | +func (i sigmaInstance) Addresses() ([]instance.Address, error) { |
2342 | + |
2343 | + ip := i.findIPv4() |
2344 | + |
2345 | + if ip == "" { |
2346 | + logger.Tracef("sigmaInstance.Addresses: IPv4 address not found") |
2347 | + return nil, instance.ErrNoDNSName |
2348 | + } |
2349 | + |
2350 | + addr := instance.Address{ |
2351 | + Value: ip, |
2352 | + Type: instance.Ipv4Address, |
2353 | + NetworkScope: instance.NetworkPublic, |
2354 | + } |
2355 | + |
2356 | + logger.Tracef("sigmaInstance.Addresses: %v", addr) |
2357 | + |
2358 | + return []instance.Address{addr}, nil |
2359 | +} |
2360 | + |
2361 | +// DNSName returns the DNS name for the instance. |
2362 | +// If the name is not yet allocated, it will return |
2363 | +// an ErrNoDNSName error. |
2364 | +func (i sigmaInstance) DNSName() (string, error) { |
2365 | + ip := i.findIPv4() |
2366 | + |
2367 | + if ip == "" { |
2368 | + logger.Tracef("sigmaInstance.DNSName: IPv4 address not found, refreshing...") |
2369 | + if err := i.Refresh(); err != nil { |
2370 | + return "", err |
2371 | + } |
2372 | + |
2373 | + ip = i.findIPv4() |
2374 | + if ip == "" { |
2375 | + return "", instance.ErrNoDNSName |
2376 | + } |
2377 | + } |
2378 | + |
2379 | + logger.Infof("sigmaInstance.DNSName: %s", ip) |
2380 | + |
2381 | + return ip, nil |
2382 | +} |
2383 | + |
2384 | +// WaitDNSName returns the DNS name for the instance, |
2385 | +// waiting until it is allocated if necessary. |
2386 | +// TODO: We may not need this in the interface any more. All |
2387 | +// implementations now delegate to environs.WaitDNSName. |
2388 | +func (i sigmaInstance) WaitDNSName() (string, error) { |
2389 | + return common.WaitDNSName(i) |
2390 | +} |
2391 | + |
2392 | +// OpenPorts opens the given ports on the instance, which |
2393 | +// should have been started with the given machine id. |
2394 | +func (i sigmaInstance) OpenPorts(machineID string, ports []instance.Port) error { |
2395 | + logger.Tracef("sigmaInstance.OpenPorts: not implemented") |
2396 | + return nil |
2397 | +} |
2398 | + |
2399 | +// ClosePorts closes the given ports on the instance, which |
2400 | +// should have been started with the given machine id. |
2401 | +func (i sigmaInstance) ClosePorts(machineID string, ports []instance.Port) error { |
2402 | + logger.Tracef("sigmaInstance.ClosePorts: not implemented") |
2403 | + return nil |
2404 | +} |
2405 | + |
2406 | +// Ports returns the set of ports open on the instance, which |
2407 | +// should have been started with the given machine id. |
2408 | +// The ports are returned as sorted by SortPorts. |
2409 | +func (i sigmaInstance) Ports(machineID string) ([]instance.Port, error) { |
2410 | + logger.Tracef("sigmaInstance.Ports: not implemented") |
2411 | + return []instance.Port{}, nil |
2412 | +} |
2413 | + |
2414 | +func (i sigmaInstance) findIPv4() string { |
2415 | + if i.server == nil { |
2416 | + return "" |
2417 | + } |
2418 | + addrs := i.server.IPv4() |
2419 | + if len(addrs) == 0 { |
2420 | + return "" |
2421 | + } |
2422 | + return addrs[0] |
2423 | +} |
2424 | + |
2425 | +func (i sigmaInstance) hardware() *instance.HardwareCharacteristics { |
2426 | + if i.server == nil { |
2427 | + return nil |
2428 | + } |
2429 | + memory := i.server.Mem() / gosigma.Megabyte |
2430 | + cores := uint64(i.server.SMP()) |
2431 | + cpu := i.server.CPU() |
2432 | + hw := instance.HardwareCharacteristics{ |
2433 | + Mem: &memory, |
2434 | + CpuCores: &cores, |
2435 | + CpuPower: &cpu, |
2436 | + } |
2437 | + return &hw |
2438 | +} |
2439 | |
2440 | === added file 'provider/cloudsigma/instance_test.go' |
2441 | --- provider/cloudsigma/instance_test.go 1970-01-01 00:00:00 +0000 |
2442 | +++ provider/cloudsigma/instance_test.go 2014-06-09 11:10:33 +0000 |
2443 | @@ -0,0 +1,328 @@ |
2444 | +// Copyright 2014 Canonical Ltd. |
2445 | +// Licensed under the AGPLv3, see LICENCE file for details. |
2446 | + |
2447 | +package cloudsigma |
2448 | + |
2449 | +import ( |
2450 | + "strings" |
2451 | + |
2452 | + "github.com/Altoros/gosigma" |
2453 | + "github.com/Altoros/gosigma/data" |
2454 | + "github.com/Altoros/gosigma/mock" |
2455 | + gc "launchpad.net/gocheck" |
2456 | + "launchpad.net/juju-core/instance" |
2457 | + "launchpad.net/juju-core/testing" |
2458 | +) |
2459 | + |
2460 | +type instanceSuite struct { |
2461 | + testing.BaseSuite |
2462 | + inst *sigmaInstance |
2463 | + instWithoutIP *sigmaInstance |
2464 | +} |
2465 | + |
2466 | +var _ = gc.Suite(&instanceSuite{}) |
2467 | + |
2468 | +func (s *instanceSuite) SetUpSuite(c *gc.C) { |
2469 | + s.BaseSuite.SetUpSuite(c) |
2470 | + mock.Start() |
2471 | +} |
2472 | + |
2473 | +func (s *instanceSuite) TearDownSuite(c *gc.C) { |
2474 | + mock.Stop() |
2475 | + s.BaseSuite.TearDownSuite(c) |
2476 | +} |
2477 | + |
2478 | +func (s *instanceSuite) SetUpTest(c *gc.C) { |
2479 | + s.BaseSuite.SetUpTest(c) |
2480 | + |
2481 | + cli, err := gosigma.NewClient(mock.Endpoint(""), mock.TestUser, mock.TestPassword, nil) |
2482 | + c.Assert(err, gc.IsNil) |
2483 | + |
2484 | + mock.ResetServers() |
2485 | + |
2486 | + ds, err := data.ReadServer(strings.NewReader(jsonInstanceData)) |
2487 | + c.Assert(err, gc.IsNil) |
2488 | + mock.AddServer(ds) |
2489 | + |
2490 | + mock.AddServer(&data.Server{ |
2491 | + Resource: data.Resource{URI: "uri", UUID: "uuid-no-ip"}, |
2492 | + }) |
2493 | + |
2494 | + server, err := cli.Server("f4ec5097-121e-44a7-a207-75bc02163260") |
2495 | + c.Assert(err, gc.IsNil) |
2496 | + c.Assert(server, gc.NotNil) |
2497 | + s.inst = &sigmaInstance{server} |
2498 | + |
2499 | + server, err = cli.Server("uuid-no-ip") |
2500 | + c.Assert(err, gc.IsNil) |
2501 | + c.Assert(server, gc.NotNil) |
2502 | + s.instWithoutIP = &sigmaInstance{server} |
2503 | +} |
2504 | + |
2505 | +func (s *instanceSuite) TearDownTest(c *gc.C) { |
2506 | + mock.ResetServers() |
2507 | + s.BaseSuite.TearDownTest(c) |
2508 | +} |
2509 | + |
2510 | +func (s *instanceSuite) TestInstanceEmpty(c *gc.C) { |
2511 | + e := sigmaInstance{} |
2512 | + c.Check(e.Id(), gc.Equals, instance.Id("")) |
2513 | + c.Check(e.Status(), gc.Equals, "") |
2514 | + c.Check(e.Refresh(), gc.ErrorMatches, "invalid instance") |
2515 | + |
2516 | + addrs, err := e.Addresses() |
2517 | + c.Check(addrs, gc.IsNil) |
2518 | + c.Check(err, gc.Equals, instance.ErrNoDNSName) |
2519 | + |
2520 | + name, err := e.DNSName() |
2521 | + c.Check(name, gc.Equals, "") |
2522 | + c.Check(err, gc.ErrorMatches, "invalid instance") |
2523 | + |
2524 | + wname, err := e.WaitDNSName() |
2525 | + c.Check(wname, gc.Equals, "") |
2526 | + c.Check(err, gc.ErrorMatches, "invalid instance") |
2527 | + |
2528 | + c.Check(e.hardware(), gc.IsNil) |
2529 | +} |
2530 | + |
2531 | +func (s *instanceSuite) TestInstanceId(c *gc.C) { |
2532 | + c.Check(s.inst.Id(), gc.Equals, instance.Id("f4ec5097-121e-44a7-a207-75bc02163260")) |
2533 | +} |
2534 | + |
2535 | +func (s *instanceSuite) TestInstanceStatus(c *gc.C) { |
2536 | + c.Check(s.inst.Status(), gc.Equals, "running") |
2537 | +} |
2538 | + |
2539 | +func (s *instanceSuite) TestInstanceRefresh(c *gc.C) { |
2540 | + c.Check(s.inst.Status(), gc.Equals, "running") |
2541 | + |
2542 | + mock.SetServerStatus("f4ec5097-121e-44a7-a207-75bc02163260", "stopped") |
2543 | + |
2544 | + err := s.inst.Refresh() |
2545 | + c.Check(err, gc.IsNil) |
2546 | + |
2547 | + c.Check(s.inst.Status(), gc.Equals, "stopped") |
2548 | +} |
2549 | + |
2550 | +func (s *instanceSuite) TestInstanceAddresses(c *gc.C) { |
2551 | + addrs, err := s.inst.Addresses() |
2552 | + c.Check(addrs, gc.HasLen, 1) |
2553 | + c.Check(err, gc.IsNil) |
2554 | + a := addrs[0] |
2555 | + c.Check(a.Value, gc.Equals, "178.22.70.33") |
2556 | + c.Check(a.Type, gc.Equals, instance.Ipv4Address) |
2557 | + c.Check(a.NetworkScope, gc.Equals, instance.NetworkPublic) |
2558 | + c.Check(a.NetworkName, gc.Equals, "") |
2559 | + |
2560 | + addrs, err = s.instWithoutIP.Addresses() |
2561 | + c.Check(addrs, gc.IsNil) |
2562 | + c.Check(err, gc.Equals, instance.ErrNoDNSName) |
2563 | +} |
2564 | + |
2565 | +func (s *instanceSuite) TestInstanceDNSName(c *gc.C) { |
2566 | + name, err := s.inst.DNSName() |
2567 | + c.Check(name, gc.Equals, "178.22.70.33") |
2568 | + c.Check(err, gc.IsNil) |
2569 | + |
2570 | + name, err = s.instWithoutIP.DNSName() |
2571 | + c.Check(name, gc.Equals, "") |
2572 | + c.Check(err, gc.Equals, instance.ErrNoDNSName) |
2573 | + |
2574 | + wname, err := s.inst.WaitDNSName() |
2575 | + c.Check(wname, gc.Equals, "178.22.70.33") |
2576 | + c.Check(err, gc.IsNil) |
2577 | + |
2578 | + go func() { |
2579 | + mock.AddServer(&data.Server{ |
2580 | + Resource: data.Resource{URI: "uri", UUID: "uuid-no-ip"}, |
2581 | + NICs: []data.NIC{ |
2582 | + data.NIC{ |
2583 | + IPv4: &data.IPv4{ |
2584 | + Conf: "static", |
2585 | + IP: data.MakeIPResource("31.171.246.37"), |
2586 | + }, |
2587 | + Runtime: &data.RuntimeNetwork{ |
2588 | + InterfaceType: "public", |
2589 | + IPv4: data.MakeIPResource("31.171.246.37"), |
2590 | + }, |
2591 | + }, |
2592 | + }, |
2593 | + }) |
2594 | + }() |
2595 | + |
2596 | + wname, err = s.instWithoutIP.WaitDNSName() |
2597 | + c.Check(wname, gc.Equals, "31.171.246.37") |
2598 | + c.Check(err, gc.IsNil) |
2599 | +} |
2600 | + |
2601 | +func (s *instanceSuite) TestInstancePorts(c *gc.C) { |
2602 | + c.Check(s.inst.OpenPorts("", nil), gc.IsNil) |
2603 | + c.Check(s.inst.ClosePorts("", nil), gc.IsNil) |
2604 | + |
2605 | + ports, err := s.inst.Ports("") |
2606 | + c.Check(ports, gc.NotNil) |
2607 | + if ports != nil { |
2608 | + c.Check(ports, gc.HasLen, 0) |
2609 | + } |
2610 | + c.Check(err, gc.IsNil) |
2611 | +} |
2612 | + |
2613 | +func (s *instanceSuite) TestInstanceHardware(c *gc.C) { |
2614 | + hw := s.inst.hardware() |
2615 | + c.Assert(hw, gc.NotNil) |
2616 | + |
2617 | + c.Check(hw.Arch, gc.IsNil) |
2618 | + |
2619 | + c.Check(hw.Mem, gc.NotNil) |
2620 | + if hw.Mem != nil { |
2621 | + c.Check(*hw.Mem, gc.Equals, uint64(2048)) |
2622 | + } |
2623 | + |
2624 | + c.Check(hw.RootDisk, gc.IsNil) |
2625 | + |
2626 | + c.Check(hw.CpuCores, gc.NotNil) |
2627 | + if hw.CpuCores != nil { |
2628 | + c.Check(*hw.CpuCores, gc.Equals, uint64(1)) |
2629 | + } |
2630 | + |
2631 | + c.Check(hw.CpuPower, gc.NotNil) |
2632 | + if hw.CpuPower != nil { |
2633 | + c.Check(*hw.CpuPower, gc.Equals, uint64(2000)) |
2634 | + } |
2635 | + |
2636 | + c.Check(hw.Tags, gc.IsNil) |
2637 | +} |
2638 | + |
2639 | +const jsonInstanceData = `{ |
2640 | + "context": true, |
2641 | + "cpu": 2000, |
2642 | + "cpu_model": null, |
2643 | + "cpus_instead_of_cores": false, |
2644 | + "drives": [ |
2645 | + { |
2646 | + "boot_order": 1, |
2647 | + "dev_channel": "0:0", |
2648 | + "device": "virtio", |
2649 | + "drive": { |
2650 | + "resource_uri": "/api/2.0/drives/f968dc48-25a0-4d46-8f16-e12e073e1fe6/", |
2651 | + "uuid": "f968dc48-25a0-4d46-8f16-e12e073e1fe6" |
2652 | + }, |
2653 | + "runtime": { |
2654 | + "io": { |
2655 | + "bytes_read": 82980352, |
2656 | + "bytes_written": 189440, |
2657 | + "count_flush": 0, |
2658 | + "count_read": 3952, |
2659 | + "count_written": 19, |
2660 | + "total_time_ns_flush": 0, |
2661 | + "total_time_ns_read": 4435322816, |
2662 | + "total_time_ns_write": 123240430 |
2663 | + } |
2664 | + } |
2665 | + } |
2666 | + ], |
2667 | + "enable_numa": false, |
2668 | + "hv_relaxed": false, |
2669 | + "hv_tsc": false, |
2670 | + "jobs": [], |
2671 | + "mem": 2147483648, |
2672 | + "meta": { |
2673 | + "description": "test-description", |
2674 | + "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDiwTGBsmFKBYHcKaVy5IgsYBR4XVYLS6KP/NKClE7gONlIGURE3+/45BX8TfHJHM5WTN8NBqJejKDHqwfyueR1f2VGoPkJxODGt/X/ZDNftLZLYwPd2DfDBs27ahOadZCk4Cl5l7mU0aoE74UnIcQoNPl6w7axkIFTIXr8+0HMk8DFB0iviBSJK118p1RGwhsoA1Hudn1CsgqARGPmNn6mxwvmQfQY7hZxZoOH9WMcvkNZ7rAFrwS/BuvEpEXkoC95K/JDPvmQVVJk7we+WeHfTYSmApkDFcSaypyjL2HOV8pvE+VntcIIhZccHiOubyjsBAx5aoTI+ueCsoz5AL1 maxim.perenesenko@altoros.com" |
2675 | + }, |
2676 | + "name": "LiveTest-srv-17-54-51-999999999", |
2677 | + "nics": [ |
2678 | + { |
2679 | + "boot_order": null, |
2680 | + "firewall_policy": null, |
2681 | + "ip_v4_conf": { |
2682 | + "conf": "dhcp", |
2683 | + "ip": null |
2684 | + }, |
2685 | + "ip_v6_conf": null, |
2686 | + "mac": "22:ab:bf:26:e1:be", |
2687 | + "model": "virtio", |
2688 | + "runtime": { |
2689 | + "interface_type": "public", |
2690 | + "io": { |
2691 | + "bytes_recv": 0, |
2692 | + "bytes_sent": 17540, |
2693 | + "packets_recv": 0, |
2694 | + "packets_sent": 256 |
2695 | + }, |
2696 | + "ip_v4": { |
2697 | + "resource_uri": "/api/2.0/ips/178.22.70.33/", |
2698 | + "uuid": "178.22.70.33" |
2699 | + }, |
2700 | + "ip_v6": null |
2701 | + }, |
2702 | + "vlan": null |
2703 | + }, |
2704 | + { |
2705 | + "boot_order": null, |
2706 | + "firewall_policy": null, |
2707 | + "ip_v4_conf": null, |
2708 | + "ip_v6_conf": null, |
2709 | + "mac": "22:9e:e7:d7:86:94", |
2710 | + "model": "virtio", |
2711 | + "runtime": { |
2712 | + "interface_type": "private", |
2713 | + "io": { |
2714 | + "bytes_recv": 0, |
2715 | + "bytes_sent": 1046, |
2716 | + "packets_recv": 0, |
2717 | + "packets_sent": 13 |
2718 | + }, |
2719 | + "ip_v4": null, |
2720 | + "ip_v6": null |
2721 | + }, |
2722 | + "vlan": { |
2723 | + "resource_uri": "/api/2.0/vlans/5bc05e7e-6555-4f40-add8-3b8e91447702/", |
2724 | + "uuid": "5bc05e7e-6555-4f40-add8-3b8e91447702" |
2725 | + } |
2726 | + } |
2727 | + ], |
2728 | + "owner": { |
2729 | + "resource_uri": "/api/2.0/user/c25eb0ed-161f-44f4-ac1d-d584ce3a5312/", |
2730 | + "uuid": "c25eb0ed-161f-44f4-ac1d-d584ce3a5312" |
2731 | + }, |
2732 | + "requirements": [], |
2733 | + "resource_uri": "/api/2.0/servers/f4ec5097-121e-44a7-a207-75bc02163260/", |
2734 | + "runtime": { |
2735 | + "active_since": "2014-04-24T14:56:58+00:00", |
2736 | + "nics": [ |
2737 | + { |
2738 | + "interface_type": "public", |
2739 | + "io": { |
2740 | + "bytes_recv": 0, |
2741 | + "bytes_sent": 17540, |
2742 | + "packets_recv": 0, |
2743 | + "packets_sent": 256 |
2744 | + }, |
2745 | + "ip_v4": { |
2746 | + "resource_uri": "/api/2.0/ips/178.22.70.33/", |
2747 | + "uuid": "178.22.70.33" |
2748 | + }, |
2749 | + "ip_v6": null, |
2750 | + "mac": "22:ab:bf:26:e1:be" |
2751 | + }, |
2752 | + { |
2753 | + "interface_type": "private", |
2754 | + "io": { |
2755 | + "bytes_recv": 0, |
2756 | + "bytes_sent": 1046, |
2757 | + "packets_recv": 0, |
2758 | + "packets_sent": 13 |
2759 | + }, |
2760 | + "ip_v4": null, |
2761 | + "ip_v6": null, |
2762 | + "mac": "22:9e:e7:d7:86:94" |
2763 | + } |
2764 | + ] |
2765 | + }, |
2766 | + "smp": 1, |
2767 | + "status": "running", |
2768 | + "tags": [], |
2769 | + "uuid": "f4ec5097-121e-44a7-a207-75bc02163260", |
2770 | + "vnc_password": "test-vnc-password" |
2771 | +}` |
2772 | |
2773 | === added file 'provider/cloudsigma/provider.go' |
2774 | --- provider/cloudsigma/provider.go 1970-01-01 00:00:00 +0000 |
2775 | +++ provider/cloudsigma/provider.go 2014-06-09 11:10:33 +0000 |
2776 | @@ -0,0 +1,140 @@ |
2777 | +// Copyright 2014 Canonical Ltd. |
2778 | +// Licensed under the AGPLv3, see LICENCE file for details. |
2779 | + |
2780 | +// Juju provider for CloudSigma |
2781 | + |
2782 | +package cloudsigma |
2783 | + |
2784 | +import ( |
2785 | + "fmt" |
2786 | + |
2787 | + "github.com/juju/loggo" |
2788 | + |
2789 | + "launchpad.net/juju-core/environs" |
2790 | + "launchpad.net/juju-core/environs/config" |
2791 | +) |
2792 | + |
2793 | +var logger = loggo.GetLogger("juju.provider.cloudsigma") |
2794 | + |
2795 | +type loggerWriter struct { |
2796 | + level loggo.Level |
2797 | +} |
2798 | + |
2799 | +func (lw loggerWriter) Write(p []byte) (n int, err error) { |
2800 | + logger.Logf(lw.level, string(p)) |
2801 | + return len(p), nil |
2802 | +} |
2803 | + |
2804 | +type environProvider struct{} |
2805 | + |
2806 | +var providerInstance = environProvider{} |
2807 | + |
2808 | +// check the provider implements environs.EnvironProvider interface |
2809 | +var _ environs.EnvironProvider = (*environProvider)(nil) |
2810 | + |
2811 | +func init() { |
2812 | + // This will only happen in binaries that actually import this provider |
2813 | + // somewhere. To enable a provider, import it in the "providers/all" |
2814 | + // package; please do *not* import individual providers anywhere else, |
2815 | + // except in direct tests for that provider. |
2816 | + environs.RegisterProvider("cloudsigma", providerInstance) |
2817 | +} |
2818 | + |
2819 | +// Boilerplate returns a default configuration for the environment in yaml format. |
2820 | +// The text should be a key followed by some number of attributes: |
2821 | +// `environName: |
2822 | +// type: environTypeName |
2823 | +// attr1: val1 |
2824 | +// ` |
2825 | +// The text is used as a template (see the template package) with one extra template |
2826 | +// function available, rand, which expands to a random hexadecimal string when invoked. |
2827 | +func (environProvider) BoilerplateConfig() string { |
2828 | + return boilerplateConfig |
2829 | +} |
2830 | + |
2831 | +// Open opens the environment and returns it. |
2832 | +// The configuration must have come from a previously |
2833 | +// prepared environment. |
2834 | +func (environProvider) Open(cfg *config.Config) (environs.Environ, error) { |
2835 | + logger.Infof("opening environment %q", cfg.Name()) |
2836 | + |
2837 | + cfg, err := prepareConfig(cfg) |
2838 | + if err != nil { |
2839 | + return nil, err |
2840 | + } |
2841 | + |
2842 | + env := &environ{name: cfg.Name()} |
2843 | + if err := env.SetConfig(cfg); err != nil { |
2844 | + return nil, err |
2845 | + } |
2846 | + |
2847 | + return env, nil |
2848 | +} |
2849 | + |
2850 | +// Prepare prepares an environment for use. Any additional |
2851 | +// configuration attributes in the returned environment should |
2852 | +// be saved to be used later. If the environment is already |
2853 | +// prepared, this call is equivalent to Open. |
2854 | +func (environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) { |
2855 | + logger.Infof("preparing environment %q", cfg.Name()) |
2856 | + return providerInstance.Open(cfg) |
2857 | +} |
2858 | + |
2859 | +// Validate ensures that config is a valid configuration for this |
2860 | +// provider, applying changes to it if necessary, and returns the |
2861 | +// validated configuration. |
2862 | +// If old is not nil, it holds the previous environment configuration |
2863 | +// for consideration when validating changes. |
2864 | +func (environProvider) Validate(cfg, old *config.Config) (*config.Config, error) { |
2865 | + logger.Infof("validating environment %q", cfg.Name()) |
2866 | + |
2867 | + // You should almost certainly not change this method; if you need to change |
2868 | + // how configs are validated, you should edit validateConfig itself, to ensure |
2869 | + // that your checks are always applied. |
2870 | + newEcfg, err := validateConfig(cfg, nil) |
2871 | + if err != nil { |
2872 | + return nil, fmt.Errorf("invalid config: %v", err) |
2873 | + } |
2874 | + if old != nil { |
2875 | + oldEcfg, err := validateConfig(old, nil) |
2876 | + if err != nil { |
2877 | + return nil, fmt.Errorf("invalid base config: %v", err) |
2878 | + } |
2879 | + if newEcfg, err = validateConfig(cfg, oldEcfg); err != nil { |
2880 | + return nil, fmt.Errorf("invalid config change: %v", err) |
2881 | + } |
2882 | + } |
2883 | + |
2884 | + return newEcfg.Config, nil |
2885 | +} |
2886 | + |
2887 | +// SecretAttrs filters the supplied configuration returning only values |
2888 | +// which are considered sensitive. All of the values of these secret |
2889 | +// attributes need to be strings. |
2890 | +func (environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) { |
2891 | + logger.Infof("filtering secret attributes for environment %q", cfg.Name()) |
2892 | + |
2893 | + // If you keep configSecretFields up to date, this method should Just Work. |
2894 | + ecfg, err := validateConfig(cfg, nil) |
2895 | + if err != nil { |
2896 | + return nil, err |
2897 | + } |
2898 | + secretAttrs := map[string]string{} |
2899 | + for _, field := range configSecretFields { |
2900 | + if value, ok := ecfg.attrs[field]; ok { |
2901 | + if stringValue, ok := value.(string); ok { |
2902 | + secretAttrs[field] = stringValue |
2903 | + } else { |
2904 | + // All your secret attributes must be strings at the moment. Sorry. |
2905 | + // It's an expedient and hopefully temporary measure that helps us |
2906 | + // plug a security hole in the API. |
2907 | + return nil, fmt.Errorf( |
2908 | + "secret %q field must have a string value; got %v", |
2909 | + field, value, |
2910 | + ) |
2911 | + } |
2912 | + } |
2913 | + } |
2914 | + |
2915 | + return secretAttrs, nil |
2916 | +} |
2917 | |
2918 | === added file 'provider/cloudsigma/provider_test.go' |
2919 | --- provider/cloudsigma/provider_test.go 1970-01-01 00:00:00 +0000 |
2920 | +++ provider/cloudsigma/provider_test.go 2014-06-09 11:10:33 +0000 |
2921 | @@ -0,0 +1,94 @@ |
2922 | +// Copyright 2014 Canonical Ltd. |
2923 | +// Licensed under the AGPLv3, see LICENCE file for details. |
2924 | + |
2925 | +package cloudsigma |
2926 | + |
2927 | +import ( |
2928 | + "flag" |
2929 | + "io" |
2930 | + "testing" |
2931 | + |
2932 | + gc "launchpad.net/gocheck" |
2933 | + tt "launchpad.net/juju-core/testing" |
2934 | + "launchpad.net/juju-core/utils" |
2935 | +) |
2936 | + |
2937 | +var live = flag.Bool("live", false, "run tests on live CloudSigma account") |
2938 | + |
2939 | +func TestCloudSigma(t *testing.T) { |
2940 | + /* |
2941 | + if *live { |
2942 | + registerLiveTests() |
2943 | + } |
2944 | + */ |
2945 | + gc.TestingT(t) |
2946 | +} |
2947 | + |
2948 | +type providerSuite struct { |
2949 | + tt.BaseSuite |
2950 | +} |
2951 | + |
2952 | +var _ = gc.Suite(&providerSuite{}) |
2953 | + |
2954 | +func (s *providerSuite) TestProviderBoilerplateConfig(c *gc.C) { |
2955 | + cfg := providerInstance.BoilerplateConfig() |
2956 | + c.Assert(cfg, gc.Not(gc.Equals), "") |
2957 | +} |
2958 | + |
2959 | +type failReader struct { |
2960 | + err error |
2961 | +} |
2962 | + |
2963 | +func (f *failReader) Read(p []byte) (n int, err error) { |
2964 | + return 0, f.err |
2965 | +} |
2966 | + |
2967 | +type fakeStorage struct { |
2968 | + call string |
2969 | + name string |
2970 | + prefix string |
2971 | + err error |
2972 | + reader io.Reader |
2973 | + length int64 |
2974 | +} |
2975 | + |
2976 | +func (f *fakeStorage) Get(name string) (io.ReadCloser, error) { |
2977 | + f.call = "Get" |
2978 | + f.name = name |
2979 | + return nil, nil |
2980 | +} |
2981 | +func (f *fakeStorage) List(prefix string) ([]string, error) { |
2982 | + f.call = "List" |
2983 | + f.prefix = prefix |
2984 | + return []string{prefix}, nil |
2985 | +} |
2986 | +func (f *fakeStorage) URL(name string) (string, error) { |
2987 | + f.call = "URL" |
2988 | + f.name = name |
2989 | + return "", nil |
2990 | +} |
2991 | +func (f *fakeStorage) DefaultConsistencyStrategy() utils.AttemptStrategy { |
2992 | + f.call = "DefaultConsistencyStrategy" |
2993 | + return utils.AttemptStrategy{} |
2994 | +} |
2995 | +func (f *fakeStorage) ShouldRetry(err error) bool { |
2996 | + f.call = "ShouldRetry" |
2997 | + f.err = err |
2998 | + return false |
2999 | +} |
3000 | +func (f *fakeStorage) Put(name string, r io.Reader, length int64) error { |
3001 | + f.call = "Put" |
3002 | + f.name = name |
3003 | + f.reader = r |
3004 | + f.length = length |
3005 | + return nil |
3006 | +} |
3007 | +func (f *fakeStorage) Remove(name string) error { |
3008 | + f.call = "Remove" |
3009 | + f.name = name |
3010 | + return nil |
3011 | +} |
3012 | +func (f *fakeStorage) RemoveAll() error { |
3013 | + f.call = "RemoveAll" |
3014 | + return nil |
3015 | +} |
3016 | |
3017 | === added file 'provider/cloudsigma/storage.go' |
3018 | --- provider/cloudsigma/storage.go 1970-01-01 00:00:00 +0000 |
3019 | +++ provider/cloudsigma/storage.go 2014-06-09 11:10:33 +0000 |
3020 | @@ -0,0 +1,252 @@ |
3021 | +// Copyright 2014 Canonical Ltd. |
3022 | +// Licensed under the AGPLv3, see LICENCE file for details. |
3023 | + |
3024 | +package cloudsigma |
3025 | + |
3026 | +import ( |
3027 | + "bytes" |
3028 | + "fmt" |
3029 | + "github.com/juju/loggo" |
3030 | + "io" |
3031 | + "io/ioutil" |
3032 | + "os" |
3033 | + "path" |
3034 | + "strings" |
3035 | + |
3036 | + "launchpad.net/juju-core/agent" |
3037 | + "launchpad.net/juju-core/environs/filestorage" |
3038 | + "launchpad.net/juju-core/environs/httpstorage" |
3039 | + "launchpad.net/juju-core/environs/sshstorage" |
3040 | + "launchpad.net/juju-core/environs/storage" |
3041 | + "launchpad.net/juju-core/juju/osenv" |
3042 | + "launchpad.net/juju-core/utils" |
3043 | +) |
3044 | + |
3045 | +const ( |
3046 | + // storageSubdir is the subdirectory of |
3047 | + // dataDir in which storage will be located. |
3048 | + storageSubdir = "storage" |
3049 | + |
3050 | + // storageTmpSubdir is the subdirectory of |
3051 | + // dataDir in which temporary storage will |
3052 | + // be located. |
3053 | + storageTmpSubdir = "storage-tmp" |
3054 | +) |
3055 | + |
3056 | +type environStorage struct { |
3057 | + storage storage.Storage |
3058 | + uuid string |
3059 | + tmp bool |
3060 | +} |
3061 | + |
3062 | +var _ storage.Storage = (*environStorage)(nil) |
3063 | + |
3064 | +var newStorage = func(ecfg *environConfig, client *environClient) (*environStorage, error) { |
3065 | + var stg storage.Storage |
3066 | + var err error |
3067 | + var tmp bool |
3068 | + |
3069 | + uuid, ip, ok := client.stateServerAddress() |
3070 | + if ok { |
3071 | + logger.Debugf("active state server: %q", ip) |
3072 | + // create https storage client |
3073 | + stg, err = newRemoteStorage(ecfg, ip) |
3074 | + } else { |
3075 | + // create tmp storage |
3076 | + tmp = true |
3077 | + path := path.Join(osenv.JujuHome(), "tmp") |
3078 | + |
3079 | + logger.Debugf("prepare %q for tmp storage", path) |
3080 | + logger.Debugf(" removing all...") |
3081 | + if err := os.RemoveAll(path); err != nil { |
3082 | + return nil, fmt.Errorf("create tmp storage (RemoveAll): %v", err) |
3083 | + } |
3084 | + |
3085 | + logger.Debugf(" creating path...") |
3086 | + if err := os.MkdirAll(path, 0755); err != nil { |
3087 | + return nil, fmt.Errorf("create tmp storage (MkdirAll): %v", err) |
3088 | + } |
3089 | + stg, err = filestorage.NewFileStorageWriter(path) |
3090 | + logger.Debugf("using local tmp storage at: %q", path) |
3091 | + } |
3092 | + |
3093 | + if err != nil { |
3094 | + return nil, err |
3095 | + } |
3096 | + |
3097 | + result := &environStorage{ |
3098 | + storage: stg, |
3099 | + uuid: uuid, |
3100 | + tmp: tmp, |
3101 | + } |
3102 | + |
3103 | + client.storage = result |
3104 | + |
3105 | + return result, nil |
3106 | +} |
3107 | + |
3108 | +func newRemoteStorage(ecfg *environConfig, ip string) (storage.Storage, error) { |
3109 | + caCertPEM, ok := ecfg.CACert() |
3110 | + if !ok { |
3111 | + // should not be possible to validate base config |
3112 | + return nil, fmt.Errorf("ca-cert not set") |
3113 | + } |
3114 | + |
3115 | + addr := fmt.Sprintf("%s:%d", ip, ecfg.storagePort()) |
3116 | + logger.Debugf("using https storage at: %q", addr) |
3117 | + |
3118 | + authkey := ecfg.storageAuthKey() |
3119 | + stg, err := httpstorage.ClientTLS(addr, caCertPEM, authkey) |
3120 | + if err != nil { |
3121 | + return nil, fmt.Errorf("initializing HTTPS storage failed: %v", err) |
3122 | + } |
3123 | + |
3124 | + return stg, nil |
3125 | +} |
3126 | + |
3127 | +func (s *environStorage) onStateInstanceStop(uuid string) { |
3128 | + if s.uuid != uuid { |
3129 | + return |
3130 | + } |
3131 | + s.storage = nil |
3132 | + s.uuid = "" |
3133 | + s.tmp = false |
3134 | +} |
3135 | + |
3136 | +func (s *environStorage) List(prefix string) ([]string, error) { |
3137 | + if s.storage == nil { |
3138 | + return nil, fmt.Errorf("storage is not initialized") |
3139 | + } |
3140 | + list, err := s.storage.List(prefix) |
3141 | + logger.Tracef("environStorage.List, prefix = %s, len = %d, err = %v", prefix, len(list), err) |
3142 | + if err == nil && logger.LogLevel() <= loggo.TRACE { |
3143 | + for _, name := range list { |
3144 | + logger.Tracef("...%q", name) |
3145 | + } |
3146 | + } |
3147 | + return list, err |
3148 | +} |
3149 | + |
3150 | +func (s *environStorage) URL(name string) (string, error) { |
3151 | + if s.storage == nil { |
3152 | + return "", fmt.Errorf("storage is not initialized") |
3153 | + } |
3154 | + if s.tmp { |
3155 | + return "%s/" + name, nil |
3156 | + } |
3157 | + url, err := s.storage.URL(name) |
3158 | + logger.Tracef("environStorage.URL, name = %q, url = %q, err = %v", name, url, err) |
3159 | + return url, err |
3160 | +} |
3161 | + |
3162 | +func (s *environStorage) Get(name string) (io.ReadCloser, error) { |
3163 | + if s.storage == nil { |
3164 | + return nil, fmt.Errorf("storage is not initialized") |
3165 | + } |
3166 | + r, err := s.storage.Get(name) |
3167 | + logger.Tracef("environStorage.Get, name = %q, err = %v", name, err) |
3168 | + return r, err |
3169 | +} |
3170 | + |
3171 | +func (s *environStorage) Put(name string, r io.Reader, length int64) error { |
3172 | + if s.storage == nil { |
3173 | + return fmt.Errorf("storage is not initialized") |
3174 | + } |
3175 | + err := s.storage.Put(name, r, length) |
3176 | + logger.Tracef("environStorage.Put, name = %q, len = %d, err = %v", name, length, err) |
3177 | + return err |
3178 | +} |
3179 | + |
3180 | +func (s *environStorage) Remove(name string) error { |
3181 | + if s.storage == nil { |
3182 | + return fmt.Errorf("storage is not initialized") |
3183 | + } |
3184 | + err := s.storage.Remove(name) |
3185 | + logger.Tracef("environStorage.Remove, name = %q, err = %v", name, err) |
3186 | + return err |
3187 | +} |
3188 | + |
3189 | +func (s *environStorage) RemoveAll() error { |
3190 | + if s.storage == nil { |
3191 | + // this method is called after state server destroy at destroy-environment |
3192 | + return nil |
3193 | + } |
3194 | + err := s.storage.RemoveAll() |
3195 | + logger.Tracef("environStorage.RemoveAll, err = %v", err) |
3196 | + return err |
3197 | +} |
3198 | + |
3199 | +func (s *environStorage) DefaultConsistencyStrategy() utils.AttemptStrategy { |
3200 | + if s.storage == nil { |
3201 | + return utils.AttemptStrategy{} |
3202 | + } |
3203 | + return s.storage.DefaultConsistencyStrategy() |
3204 | +} |
3205 | + |
3206 | +func (s *environStorage) ShouldRetry(err error) bool { |
3207 | + if s.storage == nil { |
3208 | + return false |
3209 | + } |
3210 | + return s.storage.ShouldRetry(err) |
3211 | +} |
3212 | + |
3213 | +var newSSHStorage = func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3214 | + return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{ |
3215 | + Host: sshurl, |
3216 | + StorageDir: dir, |
3217 | + TmpDir: tmpdir, |
3218 | + }) |
3219 | +} |
3220 | + |
3221 | +func (s *environStorage) MoveToSSH(user, host string) error { |
3222 | + if s.storage == nil { |
3223 | + return fmt.Errorf("storage is not initialized") |
3224 | + } |
3225 | + |
3226 | + sshurl := user + "@" + host |
3227 | + if !s.tmp { |
3228 | + return fmt.Errorf("failed to move non-temporary storage to %q", sshurl) |
3229 | + } |
3230 | + |
3231 | + storageDir := path.Join(agent.DefaultDataDir, storageSubdir) |
3232 | + storageTmpdir := path.Join(agent.DefaultDataDir, storageTmpSubdir) |
3233 | + |
3234 | + logger.Debugf("using ssh storage at host %q dir %q", sshurl, storageDir) |
3235 | + stor, err := newSSHStorage(sshurl, storageDir, storageTmpdir) |
3236 | + if err != nil { |
3237 | + return fmt.Errorf("initializing SSH storage failed: %v", err) |
3238 | + } |
3239 | + |
3240 | + list, err := s.List("") |
3241 | + if err != nil { |
3242 | + return fmt.Errorf("listing tmp storage failed: %v", err) |
3243 | + } |
3244 | + |
3245 | + logger.Tracef("list to move:\n%s", strings.Join(list, "\n")) |
3246 | + |
3247 | + for _, path := range list { |
3248 | + r, err := s.Get(path) |
3249 | + if err != nil { |
3250 | + return fmt.Errorf("getting %q from tmp storage failed: %v", path, err) |
3251 | + } |
3252 | + defer r.Close() |
3253 | + |
3254 | + bb, err := ioutil.ReadAll(r) |
3255 | + if err != nil { |
3256 | + return fmt.Errorf("error MoveToSSH: reading %q from ssh storage failed: %v", path, err) |
3257 | + } |
3258 | + |
3259 | + rb := bytes.NewReader(bb) |
3260 | + length := len(bb) |
3261 | + if err := stor.Put(path, rb, int64(length)); err != nil { |
3262 | + return fmt.Errorf("error MoveToSSH: putting %q to ssh storage failed: %v", path, err) |
3263 | + } |
3264 | + } |
3265 | + |
3266 | + s.storage.RemoveAll() |
3267 | + |
3268 | + s.storage = stor |
3269 | + s.tmp = false |
3270 | + |
3271 | + return nil |
3272 | +} |
3273 | |
3274 | === added file 'provider/cloudsigma/storage_test.go' |
3275 | --- provider/cloudsigma/storage_test.go 1970-01-01 00:00:00 +0000 |
3276 | +++ provider/cloudsigma/storage_test.go 2014-06-09 11:10:33 +0000 |
3277 | @@ -0,0 +1,373 @@ |
3278 | +// Copyright 2014 Canonical Ltd. |
3279 | +// Licensed under the AGPLv3, see LICENCE file for details. |
3280 | + |
3281 | +package cloudsigma |
3282 | + |
3283 | +import ( |
3284 | + "fmt" |
3285 | + "github.com/juju/loggo" |
3286 | + "io" |
3287 | + "io/ioutil" |
3288 | + "strings" |
3289 | + |
3290 | + gc "launchpad.net/gocheck" |
3291 | + "launchpad.net/juju-core/environs/config" |
3292 | + "launchpad.net/juju-core/environs/storage" |
3293 | + "launchpad.net/juju-core/environs/testing" |
3294 | + tt "launchpad.net/juju-core/testing" |
3295 | + "launchpad.net/juju-core/utils" |
3296 | +) |
3297 | + |
3298 | +type StorageSuite struct { |
3299 | + tt.BaseSuite |
3300 | +} |
3301 | + |
3302 | +var _ = gc.Suite(&StorageSuite{}) |
3303 | + |
3304 | +func (s *StorageSuite) TestStorageEmpty(c *gc.C) { |
3305 | + stg := &environStorage{} |
3306 | + |
3307 | + lst, err := stg.List("") |
3308 | + c.Check(lst, gc.IsNil) |
3309 | + c.Check(err, gc.NotNil) |
3310 | + |
3311 | + url, err := stg.URL("") |
3312 | + c.Check(url, gc.Equals, "") |
3313 | + c.Check(err, gc.NotNil) |
3314 | + |
3315 | + r, err := stg.Get("") |
3316 | + c.Check(r, gc.IsNil) |
3317 | + c.Check(err, gc.NotNil) |
3318 | + |
3319 | + err = stg.Put("", nil, 0) |
3320 | + c.Check(err, gc.NotNil) |
3321 | + |
3322 | + err = stg.Remove("") |
3323 | + c.Check(err, gc.NotNil) |
3324 | + |
3325 | + err = stg.RemoveAll() |
3326 | + c.Check(err, gc.IsNil) |
3327 | + |
3328 | + strategy := stg.DefaultConsistencyStrategy() |
3329 | + c.Check(strategy, gc.Equals, utils.AttemptStrategy{}) |
3330 | + |
3331 | + shr := stg.ShouldRetry(nil) |
3332 | + c.Check(shr, gc.Equals, false) |
3333 | + |
3334 | + err = stg.MoveToSSH("", "") |
3335 | + c.Check(err, gc.NotNil) |
3336 | +} |
3337 | + |
3338 | +func (s *StorageSuite) TestStorageInstanceStop(c *gc.C) { |
3339 | + stg := &environStorage{ |
3340 | + storage: &fakeStorage{}, |
3341 | + uuid: "uuid", |
3342 | + tmp: true, |
3343 | + } |
3344 | + stg.onStateInstanceStop("") |
3345 | + c.Check(stg.uuid, gc.Equals, "uuid") |
3346 | + stg.onStateInstanceStop("uuid") |
3347 | + c.Check(stg.storage, gc.IsNil) |
3348 | + c.Check(stg.uuid, gc.Equals, "") |
3349 | + c.Check(stg.tmp, gc.Equals, false) |
3350 | +} |
3351 | + |
3352 | +func (s *StorageSuite) TestStorageURL(c *gc.C) { |
3353 | + fs := &fakeStorage{} |
3354 | + stg := &environStorage{ |
3355 | + storage: fs, |
3356 | + uuid: "uuid", |
3357 | + tmp: true, |
3358 | + } |
3359 | + |
3360 | + url, err := stg.URL("") |
3361 | + c.Check(err, gc.IsNil) |
3362 | + c.Check(url, gc.Equals, "%s/") |
3363 | + |
3364 | + url, err = stg.URL("path/name") |
3365 | + c.Check(err, gc.IsNil) |
3366 | + c.Check(url, gc.Equals, "%s/path/name") |
3367 | + |
3368 | + stg.tmp = false |
3369 | + |
3370 | + url, err = stg.URL("path/name") |
3371 | + c.Check(err, gc.IsNil) |
3372 | + c.Check(url, gc.Equals, "") |
3373 | + c.Check(fs.call, gc.Equals, "URL") |
3374 | + c.Check(fs.name, gc.Equals, "path/name") |
3375 | +} |
3376 | + |
3377 | +func (s *StorageSuite) TestStorageProxy(c *gc.C) { |
3378 | + fs := &fakeStorage{} |
3379 | + stg := &environStorage{ |
3380 | + storage: fs, |
3381 | + uuid: "uuid", |
3382 | + tmp: true, |
3383 | + } |
3384 | + |
3385 | + test := func(s storage.Storage) { |
3386 | + |
3387 | + ll := logger.LogLevel() |
3388 | + logger.SetLogLevel(loggo.TRACE) |
3389 | + s.List("list") |
3390 | + c.Check(fs.call, gc.Equals, "List") |
3391 | + c.Check(fs.prefix, gc.Equals, "list") |
3392 | + logger.SetLogLevel(ll) |
3393 | + |
3394 | + s.Get("get") |
3395 | + c.Check(fs.call, gc.Equals, "Get") |
3396 | + c.Check(fs.name, gc.Equals, "get") |
3397 | + |
3398 | + r := strings.NewReader("") |
3399 | + s.Put("put", r, 1024) |
3400 | + c.Check(fs.call, gc.Equals, "Put") |
3401 | + c.Check(fs.name, gc.Equals, "put") |
3402 | + c.Check(fs.reader, gc.Equals, r) |
3403 | + c.Check(fs.length, gc.Equals, int64(1024)) |
3404 | + |
3405 | + s.Remove("remove") |
3406 | + c.Check(fs.call, gc.Equals, "Remove") |
3407 | + c.Check(fs.name, gc.Equals, "remove") |
3408 | + |
3409 | + s.RemoveAll() |
3410 | + c.Check(fs.call, gc.Equals, "RemoveAll") |
3411 | + |
3412 | + s.DefaultConsistencyStrategy() |
3413 | + c.Check(fs.call, gc.Equals, "DefaultConsistencyStrategy") |
3414 | + |
3415 | + err := fmt.Errorf("test") |
3416 | + s.ShouldRetry(err) |
3417 | + c.Check(fs.call, gc.Equals, "ShouldRetry") |
3418 | + c.Check(fs.err, gc.Equals, err) |
3419 | + } |
3420 | + |
3421 | + test(stg) |
3422 | + |
3423 | + stg.tmp = false |
3424 | + test(stg) |
3425 | +} |
3426 | + |
3427 | +func (s *StorageSuite) TestStorageMoveNonTemp(c *gc.C) { |
3428 | + fs := &fakeStorage{} |
3429 | + stg := &environStorage{ |
3430 | + storage: fs, |
3431 | + uuid: "uuid", |
3432 | + tmp: false, |
3433 | + } |
3434 | + |
3435 | + err := stg.MoveToSSH("", "") |
3436 | + c.Check(err, gc.NotNil) |
3437 | +} |
3438 | + |
3439 | +func (s *StorageSuite) TestStorageMoveFailSsh(c *gc.C) { |
3440 | + fs := &fakeStorage{} |
3441 | + stg := &environStorage{ |
3442 | + storage: fs, |
3443 | + uuid: "uuid", |
3444 | + tmp: true, |
3445 | + } |
3446 | + |
3447 | + s.PatchValue(&newSSHStorage, |
3448 | + func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3449 | + return nil, fmt.Errorf("test") |
3450 | + }) |
3451 | + |
3452 | + err := stg.MoveToSSH("", "") |
3453 | + c.Check(err, gc.NotNil) |
3454 | + c.Check(err, gc.ErrorMatches, "initializing SSH storage failed: test") |
3455 | +} |
3456 | + |
3457 | +func (s *StorageSuite) TestStorageMoveFailList(c *gc.C) { |
3458 | + fs := &fakeStorage{} |
3459 | + stg := &environStorage{ |
3460 | + storage: fs, |
3461 | + uuid: "uuid", |
3462 | + tmp: true, |
3463 | + } |
3464 | + |
3465 | + s.PatchValue(&newSSHStorage, |
3466 | + func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3467 | + stg.storage = nil |
3468 | + return nil, nil |
3469 | + }) |
3470 | + |
3471 | + err := stg.MoveToSSH("", "") |
3472 | + c.Check(err, gc.NotNil) |
3473 | + c.Check(err, gc.ErrorMatches, "listing tmp storage failed: storage is not initialized") |
3474 | +} |
3475 | + |
3476 | +func newTestStorage(s *StorageSuite, c *gc.C) storage.Storage { |
3477 | + closer, stor, _ := testing.CreateLocalTestStorage(c) |
3478 | + s.AddCleanup(func(*gc.C) { closer.Close() }) |
3479 | + return stor |
3480 | +} |
3481 | + |
3482 | +type moveStorageTest struct { |
3483 | + stg storage.Storage |
3484 | + handler func(_, _, _ string) (storage.Storage, error) |
3485 | + data map[string]string |
3486 | + emsg string |
3487 | +} |
3488 | + |
3489 | +func (s *StorageSuite) testMoveStorage(c *gc.C, d *moveStorageTest) { |
3490 | + |
3491 | + s.PatchValue(&newSSHStorage, d.handler) |
3492 | + |
3493 | + for k, v := range d.data { |
3494 | + err := d.stg.Put(k, strings.NewReader(v), int64(len(v))) |
3495 | + c.Assert(err, gc.IsNil) |
3496 | + } |
3497 | + |
3498 | + src := &environStorage{ |
3499 | + storage: d.stg, |
3500 | + uuid: "uuid", |
3501 | + tmp: true, |
3502 | + } |
3503 | + |
3504 | + err := src.MoveToSSH("user", "host") |
3505 | + if d.emsg == "" { |
3506 | + c.Check(err, gc.IsNil) |
3507 | + } else { |
3508 | + c.Check(err, gc.ErrorMatches, d.emsg) |
3509 | + return |
3510 | + } |
3511 | + |
3512 | + kk, err := src.storage.List("") |
3513 | + c.Assert(err, gc.IsNil) |
3514 | + |
3515 | + var result = make(map[string]string, len(kk)) |
3516 | + for _, k := range kk { |
3517 | + r, err := src.storage.Get(k) |
3518 | + c.Assert(err, gc.IsNil) |
3519 | + defer r.Close() |
3520 | + bb, err := ioutil.ReadAll(r) |
3521 | + c.Assert(err, gc.IsNil) |
3522 | + result[k] = string(bb) |
3523 | + } |
3524 | + |
3525 | + for k, v := range d.data { |
3526 | + if vv, ok := result[k]; !ok { |
3527 | + c.Errorf("key %s not found", k) |
3528 | + } else { |
3529 | + c.Check(vv, gc.Equals, v) |
3530 | + } |
3531 | + } |
3532 | +} |
3533 | + |
3534 | +func (s *StorageSuite) TestStorageMoveEmpty(c *gc.C) { |
3535 | + data := &moveStorageTest{ |
3536 | + stg: newTestStorage(s, c), |
3537 | + handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3538 | + c.Check(sshurl, gc.Equals, "user@host") |
3539 | + return newTestStorage(s, c), nil |
3540 | + }} |
3541 | + s.testMoveStorage(c, data) |
3542 | +} |
3543 | + |
3544 | +var moveData = map[string]string{ |
3545 | + "test0": "0987654321", |
3546 | + "test/test1": "1234567890", |
3547 | +} |
3548 | + |
3549 | +func (s *StorageSuite) TestStorageMoveData(c *gc.C) { |
3550 | + data := &moveStorageTest{ |
3551 | + stg: newTestStorage(s, c), |
3552 | + handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3553 | + c.Check(sshurl, gc.Equals, "user@host") |
3554 | + return newTestStorage(s, c), nil |
3555 | + }, |
3556 | + data: moveData, |
3557 | + } |
3558 | + s.testMoveStorage(c, data) |
3559 | +} |
3560 | + |
3561 | +type storageProxyGetFailed struct { |
3562 | + storage.Storage |
3563 | +} |
3564 | + |
3565 | +func (s *storageProxyGetFailed) Get(name string) (io.ReadCloser, error) { |
3566 | + return nil, fmt.Errorf("test") |
3567 | +} |
3568 | + |
3569 | +func (s *StorageSuite) TestStorageMoveGetFailed(c *gc.C) { |
3570 | + data := &moveStorageTest{ |
3571 | + stg: &storageProxyGetFailed{newTestStorage(s, c)}, |
3572 | + handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3573 | + c.Check(sshurl, gc.Equals, "user@host") |
3574 | + return newTestStorage(s, c), nil |
3575 | + }, |
3576 | + data: moveData, |
3577 | + emsg: "getting .* from tmp storage failed: test", |
3578 | + } |
3579 | + s.testMoveStorage(c, data) |
3580 | +} |
3581 | + |
3582 | +type storageProxyGetReadFailed struct { |
3583 | + storage.Storage |
3584 | +} |
3585 | + |
3586 | +func (s *storageProxyGetReadFailed) Get(name string) (io.ReadCloser, error) { |
3587 | + err := fmt.Errorf("test") |
3588 | + r := &failReader{err} |
3589 | + return ioutil.NopCloser(r), nil |
3590 | +} |
3591 | + |
3592 | +func (s *StorageSuite) TestStorageMoveGetReadFailed(c *gc.C) { |
3593 | + data := &moveStorageTest{ |
3594 | + stg: &storageProxyGetReadFailed{newTestStorage(s, c)}, |
3595 | + handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3596 | + c.Check(sshurl, gc.Equals, "user@host") |
3597 | + return newTestStorage(s, c), nil |
3598 | + }, |
3599 | + data: moveData, |
3600 | + emsg: ".*MoveToSSH: reading .* from ssh storage failed: test", |
3601 | + } |
3602 | + s.testMoveStorage(c, data) |
3603 | +} |
3604 | + |
3605 | +type storageProxyPutFailed struct { |
3606 | + storage.Storage |
3607 | +} |
3608 | + |
3609 | +func (s *storageProxyPutFailed) Put(name string, r io.Reader, length int64) error { |
3610 | + return fmt.Errorf("test") |
3611 | +} |
3612 | + |
3613 | +func (s *StorageSuite) TestStorageMovePutFailed(c *gc.C) { |
3614 | + data := &moveStorageTest{ |
3615 | + stg: newTestStorage(s, c), |
3616 | + handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) { |
3617 | + c.Check(sshurl, gc.Equals, "user@host") |
3618 | + return &storageProxyPutFailed{newTestStorage(s, c)}, nil |
3619 | + }, |
3620 | + data: moveData, |
3621 | + emsg: ".*MoveToSSH: putting .* to ssh storage failed: test", |
3622 | + } |
3623 | + s.testMoveStorage(c, data) |
3624 | +} |
3625 | + |
3626 | +func (s *StorageSuite) TestStorageNewRemoteStorage(c *gc.C) { |
3627 | + ecfg := &environConfig{ |
3628 | + Config: newConfig(c, tt.Attrs{ |
3629 | + "name": "client-test", |
3630 | + }), |
3631 | + attrs: map[string]interface{}{ |
3632 | + "storage-port": 1234, |
3633 | + }, |
3634 | + } |
3635 | + stg, err := newRemoteStorage(ecfg, "0.1.2.3") |
3636 | + c.Check(stg, gc.NotNil) |
3637 | + c.Check(err, gc.IsNil) |
3638 | + |
3639 | + attrs := tt.FakeConfig().Delete("ca-cert", "ca-private-key") |
3640 | + cfg, err := config.New(config.NoDefaults, attrs) |
3641 | + c.Assert(err, gc.IsNil) |
3642 | + ecfg = &environConfig{ |
3643 | + Config: cfg, |
3644 | + attrs: map[string]interface{}{ |
3645 | + "storage-port": 1234, |
3646 | + }} |
3647 | + stg, err = newRemoteStorage(ecfg, "0.1.2.3") |
3648 | + c.Check(stg, gc.IsNil) |
3649 | + c.Check(err, gc.ErrorMatches, "ca-cert not set") |
3650 | +} |
3651 | |
3652 | === added file 'provider/cloudsigma/storageconfig.go' |
3653 | --- provider/cloudsigma/storageconfig.go 1970-01-01 00:00:00 +0000 |
3654 | +++ provider/cloudsigma/storageconfig.go 2014-06-09 11:10:33 +0000 |
3655 | @@ -0,0 +1,59 @@ |
3656 | +// Copyright 2014 Canonical Ltd. |
3657 | +// Licensed under the AGPLv3, see LICENCE file for details. |
3658 | + |
3659 | +package cloudsigma |
3660 | + |
3661 | +import ( |
3662 | + "fmt" |
3663 | + |
3664 | + "launchpad.net/juju-core/worker/localstorage" |
3665 | +) |
3666 | + |
3667 | +// storageConfig is an struct implementing LocalTLSStorageConfig interface |
3668 | +// to support serving storage over TLS. |
3669 | +type storageConfig struct { |
3670 | + ecfg *environConfig |
3671 | + storageDir string |
3672 | + storageAddr string |
3673 | + storagePort int |
3674 | +} |
3675 | + |
3676 | +var _ localstorage.LocalTLSStorageConfig = (*storageConfig)(nil) |
3677 | + |
3678 | +// StorageDir is a storage local directory |
3679 | +func (c storageConfig) StorageDir() string { |
3680 | + return c.storageDir |
3681 | +} |
3682 | + |
3683 | +// StorageAddr is a storage IP address and port |
3684 | +func (c storageConfig) StorageAddr() string { |
3685 | + return fmt.Sprintf("%s:%d", c.storageAddr, c.storagePort) |
3686 | +} |
3687 | + |
3688 | +// StorageCACert is the CA certificate in PEM format. |
3689 | +func (c storageConfig) StorageCACert() string { |
3690 | + if cert, ok := c.ecfg.CACert(); ok { |
3691 | + return cert |
3692 | + } |
3693 | + return "" |
3694 | +} |
3695 | + |
3696 | +// StorageCAKey is the CA private key in PEM format. |
3697 | +func (c storageConfig) StorageCAKey() string { |
3698 | + if key, ok := c.ecfg.CAPrivateKey(); ok { |
3699 | + return key |
3700 | + } |
3701 | + return "" |
3702 | +} |
3703 | + |
3704 | +// StorageHostnames is the set of hostnames that will |
3705 | +// be assigned to the storage server's certificate. |
3706 | +func (c storageConfig) StorageHostnames() []string { |
3707 | + return []string{c.storageAddr} |
3708 | +} |
3709 | + |
3710 | +// StorageAuthKey is the key that clients must present |
3711 | +// to perform modifying operations. |
3712 | +func (c storageConfig) StorageAuthKey() string { |
3713 | + return c.ecfg.storageAuthKey() |
3714 | +} |
3715 | |
3716 | === added file 'provider/cloudsigma/storageconfig_test.go' |
3717 | --- provider/cloudsigma/storageconfig_test.go 1970-01-01 00:00:00 +0000 |
3718 | +++ provider/cloudsigma/storageconfig_test.go 2014-06-09 11:10:33 +0000 |
3719 | @@ -0,0 +1,58 @@ |
3720 | +// Copyright 2014 Canonical Ltd. |
3721 | +// Licensed under the AGPLv3, see LICENCE file for details. |
3722 | + |
3723 | +package cloudsigma |
3724 | + |
3725 | +import ( |
3726 | + gc "launchpad.net/gocheck" |
3727 | + "launchpad.net/juju-core/environs/config" |
3728 | + "launchpad.net/juju-core/testing" |
3729 | +) |
3730 | + |
3731 | +func newStorageConfig(c *gc.C, attrs testing.Attrs) *storageConfig { |
3732 | + attrs = testing.FakeConfig().Merge(attrs) |
3733 | + cfg, err := config.New(config.NoDefaults, attrs) |
3734 | + c.Assert(err, gc.IsNil) |
3735 | + ecfg, err := validateConfig(cfg, nil) |
3736 | + c.Assert(err, gc.IsNil) |
3737 | + return &storageConfig{ecfg: ecfg} |
3738 | +} |
3739 | + |
3740 | +type StorageConfigSuite struct { |
3741 | + testing.BaseSuite |
3742 | +} |
3743 | + |
3744 | +var _ = gc.Suite(&StorageConfigSuite{}) |
3745 | + |
3746 | +func (s *StorageConfigSuite) TestStorageConfig(c *gc.C) { |
3747 | + cfg := newStorageConfig(c, validAttrs()) |
3748 | + cfg.storageDir = "dir" |
3749 | + cfg.storageAddr = "addr" |
3750 | + cfg.storagePort = 8080 |
3751 | + c.Check(cfg.StorageDir(), gc.Equals, "dir") |
3752 | + c.Check(cfg.StorageAddr(), gc.Equals, "addr:8080") |
3753 | + c.Check(cfg.StorageCACert(), gc.Equals, testing.CACert) |
3754 | + c.Check(cfg.StorageCAKey(), gc.Equals, testing.CAKey) |
3755 | + c.Check(cfg.StorageAuthKey(), gc.Equals, "ABCDEFGH") |
3756 | + |
3757 | + hostnames := cfg.StorageHostnames() |
3758 | + c.Assert(hostnames, gc.HasLen, 1) |
3759 | + c.Check(hostnames[0], gc.Equals, "addr") |
3760 | +} |
3761 | + |
3762 | +func (s *StorageConfigSuite) TestStorageConfigEmpty(c *gc.C) { |
3763 | + cfg := storageConfig{ |
3764 | + ecfg: &environConfig{ |
3765 | + Config: &config.Config{}, |
3766 | + }, |
3767 | + } |
3768 | + c.Check(cfg.StorageDir(), gc.Equals, "") |
3769 | + c.Check(cfg.StorageAddr(), gc.Equals, ":0") |
3770 | + c.Check(cfg.StorageCACert(), gc.Equals, "") |
3771 | + c.Check(cfg.StorageCAKey(), gc.Equals, "") |
3772 | + c.Check(cfg.StorageAuthKey(), gc.Equals, "") |
3773 | + |
3774 | + hostnames := cfg.StorageHostnames() |
3775 | + c.Assert(hostnames, gc.HasLen, 1) |
3776 | + c.Check(hostnames[0], gc.Equals, "") |
3777 | +} |
I made a *very* few first-pass comments, but it's really going to be
hard to review all this in one go. Would you please break it up a
little? I'd recommend starting with the config type first and leaving
the Environ implementation out for a while...
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config. go cloudsigma/ config. go (right):
File provider/
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config. go#newcode63 cloudsigma/ config. go:63: "storage-auth-key",
provider/
I'm pretty sure region should be immutable.
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config. go#newcode125 cloudsigma/ config. go:125: // to ensure the object we return
provider/
icfgs internally consistent.
s/icfgs/is/
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go cloudsigma/ config_ test.go (right):
File provider/
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go# newcode62 cloudsigma/ config_ test.go: 62: var newConfigTests = []struct {
provider/
Ideally, put these definitions inside the test method. We've found
there's a distressing tendency for people to abuse them when they're
global :(.
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go# newcode71 cloudsigma/ config_ test.go: 71: err: ".* must not be empty.*",
provider/
Please don't use .* in tests except when you *really* have to. It masks
all kinds of embarrassments ;).
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go# newcode159 cloudsigma/ config_ test.go: 159: info: "can change region",
provider/
I don't think this is meaningful.
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go# newcode237 cloudsigma/ config_ test.go: 237: fail := fmt.Errorf( "error" )}
provider/
failReader{
This sort of test makes me happy, thank you.
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go# newcode286 cloudsigma/ config_ test.go: 286: err: ".* must not be
provider/
empty.*",
as above, please avoid unless *really* necessary
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ config_ test.go# newcode319 cloudsigma/ config_ test.go: 319: c.Errorf("secrect field %s not
provider/
found in configFields", field)
s/secrect/secret/
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ constraints_ test.go cloudsigma/ constraints_ test.go (right):
File provider/
https:/ /codereview. appspot. com/101970045/ diff/20001/ provider/ cloudsigma/ constraints_ test.go# newcode94 cloudsigma/ constraints_ test.go: 94: c.Logf("test (%d): %+v", i,
provider/
t)
It's generally more convenient to log all the tests as they go past...
and it's nice to get the test description *before* the failure.
https:/ /codereview. appspot. com/101970045/