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