Merge lp:~maxim-perenesenko/juju-core/cloudsigma into lp:~go-bot/juju-core/trunk

Proposed by Maxim Perenesenko
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
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+221540@code.launchpad.net

Description of the change

Added provider for CloudSigma cloud.

Provider for CloudSigma (https://www.cloudsigma.com) uses CloudSigma API v2.0
(https://cloudsigma-docs.readthedocs.org/en/2.10/).

Minimal client for communicating with the CloudSigma Web API from Go program was implemented as
separate library github.com/Altoros/gosigma. This library is published under AGPLv3 license.

gosigma library is added as dependency to dependencies.tsv file.

https://codereview.appspot.com/101970045/

To post a comment you must log in.
Revision history for this message
William Reade (fwereade) wrote :

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
File provider/cloudsigma/config.go (right):

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config.go#newcode63
provider/cloudsigma/config.go:63: "storage-auth-key",
I'm pretty sure region should be immutable.

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config.go#newcode125
provider/cloudsigma/config.go:125: // to ensure the object we return
icfgs internally consistent.
s/icfgs/is/

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config_test.go
File provider/cloudsigma/config_test.go (right):

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config_test.go#newcode62
provider/cloudsigma/config_test.go:62: var newConfigTests = []struct {
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
provider/cloudsigma/config_test.go:71: err: ".* must not be empty.*",
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
provider/cloudsigma/config_test.go:159: info: "can change region",
I don't think this is meaningful.

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config_test.go#newcode237
provider/cloudsigma/config_test.go:237: fail :=
failReader{fmt.Errorf("error")}
This sort of test makes me happy, thank you.

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config_test.go#newcode286
provider/cloudsigma/config_test.go:286: err: ".* must not be
empty.*",
as above, please avoid unless *really* necessary

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/config_test.go#newcode319
provider/cloudsigma/config_test.go:319: c.Errorf("secrect field %s not
found in configFields", field)
s/secrect/secret/

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/constraints_test.go
File provider/cloudsigma/constraints_test.go (right):

https://codereview.appspot.com/101970045/diff/20001/provider/cloudsigma/constraints_test.go#newcode94
provider/cloudsigma/constraints_test.go:94: c.Logf("test (%d): %+v", i,
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/

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/jujud/machine.go'
--- cmd/jujud/machine.go 2014-06-02 21:26:40 +0000
+++ cmd/jujud/machine.go 2014-06-09 11:10:33 +0000
@@ -433,7 +433,9 @@
433 // Take advantage of special knowledge here in that we will only ever want433 // Take advantage of special knowledge here in that we will only ever want
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.
435 providerType := agentConfig.Value(agent.ProviderType)435 providerType := agentConfig.Value(agent.ProviderType)
436 if (providerType == provider.Local || provider.IsManual(providerType)) && m.Id() == bootstrapMachineId {436 if (providerType == provider.Local ||
437 provider.IsManual(providerType) ||
438 providerType == "cloudsigma") && m.Id() == bootstrapMachineId {
437 a.startWorkerAfterUpgrade(runner, "local-storage", func() (worker.Worker, error) {439 a.startWorkerAfterUpgrade(runner, "local-storage", func() (worker.Worker, error) {
438 // TODO(axw) 2013-09-24 bug #1229507440 // TODO(axw) 2013-09-24 bug #1229507
439 // Make another job to enable storage.441 // Make another job to enable storage.
440442
=== modified file 'dependencies.tsv'
--- dependencies.tsv 2014-06-02 15:47:39 +0000
+++ dependencies.tsv 2014-06-09 11:10:33 +0000
@@ -1,5 +1,6 @@
1code.google.com/p/go.crypto hg 6478cc9340cbbe6c04511280c5007722269108e9 1841code.google.com/p/go.crypto hg 6478cc9340cbbe6c04511280c5007722269108e9 184
2code.google.com/p/go.net hg c17ad62118ea511e1051721b429779fa40bddc74 1162code.google.com/p/go.net hg c17ad62118ea511e1051721b429779fa40bddc74 116
3github.com/Altoros/gosigma git c56c52eb785e48ec0f7fcc0da385c85210c7ff3d
3github.com/binary132/gojsonpointer git 57ab5e9c764219a3e0c4d7759797fefdcab22e9c 4github.com/binary132/gojsonpointer git 57ab5e9c764219a3e0c4d7759797fefdcab22e9c
4github.com/binary132/gojsonreference git 75785fb7b21f9bf2051dca600da83ff57bc6582a 5github.com/binary132/gojsonreference git 75785fb7b21f9bf2051dca600da83ff57bc6582a
5github.com/binary132/gojsonschema git 640782bf48d45ba2b22fd1e8a80842667c52af00 6github.com/binary132/gojsonschema git 640782bf48d45ba2b22fd1e8a80842667c52af00
67
=== modified file 'provider/all/all.go'
--- provider/all/all.go 2014-04-02 11:35:49 +0000
+++ provider/all/all.go 2014-06-09 11:10:33 +0000
@@ -6,6 +6,7 @@
6// Register all the available providers.6// Register all the available providers.
7import (7import (
8 _ "launchpad.net/juju-core/provider/azure"8 _ "launchpad.net/juju-core/provider/azure"
9 _ "launchpad.net/juju-core/provider/cloudsigma"
9 _ "launchpad.net/juju-core/provider/ec2"10 _ "launchpad.net/juju-core/provider/ec2"
10 _ "launchpad.net/juju-core/provider/joyent"11 _ "launchpad.net/juju-core/provider/joyent"
11 _ "launchpad.net/juju-core/provider/local"12 _ "launchpad.net/juju-core/provider/local"
1213
=== added directory 'provider/cloudsigma'
=== added file 'provider/cloudsigma/client.go'
--- provider/cloudsigma/client.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/client.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,307 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8 "launchpad.net/juju-core/environs"
9 "launchpad.net/juju-core/instance"
10 "launchpad.net/juju-core/utils"
11 "strings"
12
13 "github.com/Altoros/gosigma"
14 "github.com/juju/loggo"
15)
16
17// This file contains implementation of CloudSigma client.
18type environClient struct {
19 conn *gosigma.Client
20 region string
21 username string
22 password string
23 name string
24 storage *environStorage
25}
26
27type tracer struct{}
28
29func (tracer) Logf(format string, args ...interface{}) {
30 logger.Tracef(format, args...)
31}
32
33// newClient creates new CloudSigma client connection.
34var newClient = func(cfg *environConfig) (*environClient, error) {
35
36 // fetch and validate configuration
37 region, err := gosigma.ResolveEndpoint(cfg.region())
38 if err != nil {
39 region = cfg.region()
40 }
41 username := cfg.username()
42 password := cfg.password()
43 name := cfg.Name()
44
45 logger.Debugf("creating CloudSigma client: region=%s, user=%s, password=%s, name=%q",
46 region, username, strings.Repeat("*", len(password)), name)
47
48 // create connection to CloudSigma
49 conn, err := gosigma.NewClient(region, username, password, nil)
50 if err != nil {
51 return nil, err
52 }
53
54 // configure trace logger
55 if logger.LogLevel() <= loggo.TRACE {
56 conn.Logger(&tracer{})
57 }
58
59 c := &environClient{
60 conn: conn,
61 region: region,
62 name: name,
63 username: username,
64 password: password,
65 }
66
67 return c, nil
68}
69
70// configChanged checks if CloudSigma client environment configuration is changed
71func (c environClient) configChanged(cfg *environConfig) bool {
72 // fetch configuration
73 region, err := gosigma.ResolveEndpoint(cfg.region())
74 if err != nil {
75 return true
76 }
77 username := cfg.username()
78 password := cfg.password()
79 name := cfg.Name()
80
81 // compare
82 if region != c.region || username != c.username ||
83 password != c.password || name != c.name {
84 return true
85 }
86
87 return false
88}
89
90const (
91 jujuMetaInstance = "juju-instance"
92 jujuMetaInstanceStateServer = "state-server"
93 jujuMetaInstanceServer = "server"
94
95 jujuMetaEnvironment = "juju-environment"
96)
97
98func (c environClient) isMyEnvironment(s gosigma.Server) bool {
99 if v, _ := s.Get(jujuMetaEnvironment); c.name == v {
100 return true
101 }
102 return false
103}
104
105func (c environClient) isMyServer(s gosigma.Server) bool {
106 if _, ok := s.Get(jujuMetaInstance); ok {
107 return c.isMyEnvironment(s)
108 }
109 return false
110}
111
112func (c environClient) isMyStateServer(s gosigma.Server) bool {
113 if v, ok := s.Get(jujuMetaInstance); ok && v == jujuMetaInstanceStateServer {
114 return c.isMyEnvironment(s)
115 }
116 return false
117}
118
119// instances of servers at CloudSigma account
120func (c environClient) instances() ([]gosigma.Server, error) {
121 return c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer)
122}
123
124// instanceMap of server ids to servers at CloudSigma account
125func (c environClient) instanceMap() (map[string]gosigma.Server, error) {
126 servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyServer)
127 if err != nil {
128 return nil, err
129 }
130
131 m := make(map[string]gosigma.Server, len(servers))
132 for _, s := range servers {
133 m[s.UUID()] = s
134 }
135
136 return m, nil
137}
138
139func (c environClient) stateServerAddress() (string, string, bool) {
140 logger.Debugf("query state...")
141
142 servers, err := c.conn.ServersFiltered(gosigma.RequestDetail, c.isMyStateServer)
143 if err != nil {
144 return "", "", false
145 }
146
147 logger.Debugf("...servers count: %d", len(servers))
148 if logger.LogLevel() <= loggo.TRACE {
149 for _, s := range servers {
150 logger.Tracef("... %s", s)
151 }
152 }
153
154 for _, s := range servers {
155 if s.Status() != gosigma.ServerRunning {
156 continue
157 }
158 if addrs := s.IPv4(); len(addrs) != 0 {
159 return s.UUID(), addrs[0], true
160 }
161 }
162
163 return "", "", false
164}
165
166// stop instance
167func (c environClient) stopInstance(id instance.Id) error {
168 uuid := string(id)
169 if uuid == "" {
170 return fmt.Errorf("invalid instance id")
171 }
172
173 var err error
174
175 s, err := c.conn.Server(uuid)
176 if err != nil {
177 return err
178 }
179
180 if c.storage != nil && c.isMyStateServer(s) {
181 c.storage.onStateInstanceStop(uuid)
182 }
183
184 err = s.StopWait()
185 logger.Tracef("environClient.StopInstance - stop server, %q = %v", uuid, err)
186
187 err = s.Remove(gosigma.RecurseAllDrives)
188 logger.Tracef("environClient.StopInstance - remove server, %q = %v", uuid, err)
189
190 return nil
191}
192
193// start new instance
194func (c environClient) newInstance(args environs.StartInstanceParams) (srv gosigma.Server, drv gosigma.Drive, err error) {
195
196 cleanup := func() {
197 if err == nil {
198 return
199 }
200 if srv != nil {
201 srv.Remove(gosigma.RecurseAllDrives)
202 } else if drv != nil {
203 drv.Remove()
204 }
205 srv = nil
206 drv = nil
207 }
208 defer cleanup()
209
210 if args.MachineConfig == nil {
211 err = fmt.Errorf("invalid configuration for new instance")
212 return
213 }
214
215 logger.Tracef("Tools: %v", args.Tools.URLs())
216 logger.Tracef("Juju Constraints:" + args.Constraints.String())
217 logger.Tracef("MachineConfig: %#v", args.MachineConfig)
218
219 cs, err := newConstraints(args.MachineConfig.Bootstrap,
220 args.Constraints, args.MachineConfig.Tools.Version.Series)
221 if err != nil {
222 return
223 }
224 logger.Debugf("CloudSigma Constraints: %v", cs)
225
226 originalDrive, err := c.conn.Drive(cs.driveTemplate, gosigma.LibraryMedia)
227 if err != nil {
228 err = fmt.Errorf("query drive template: %v", err)
229 return
230 }
231
232 baseName := "juju-" + c.name + "-" + args.MachineConfig.MachineId
233
234 cloneParams := gosigma.CloneParams{Name: baseName}
235 if drv, err = originalDrive.CloneWait(cloneParams, nil); err != nil {
236 err = fmt.Errorf("error cloning drive: %v", err)
237 return
238 }
239
240 if drv.Size() < cs.driveSize {
241 if err = drv.ResizeWait(cs.driveSize); err != nil {
242 err = fmt.Errorf("error resizing drive: %v", err)
243 return
244 }
245 }
246
247 var cc gosigma.Components
248 cc.SetName(baseName)
249 cc.SetDescription(baseName)
250
251 if cs.cores != 0 {
252 cc.SetSMP(cs.cores)
253 }
254
255 if cs.power != 0 {
256 cc.SetCPU(cs.power)
257 }
258
259 if cs.mem != 0 {
260 cc.SetMem(cs.mem)
261 }
262
263 vncpass, err := utils.RandomPassword()
264 if err != nil {
265 err = fmt.Errorf("error generating password: %v", err)
266 return
267 }
268 cc.SetVNCPassword(vncpass)
269
270 cc.SetSSHPublicKey(args.MachineConfig.AuthorizedKeys)
271 cc.AttachDrive(1, "0:0", "virtio", drv.UUID())
272 cc.NetworkDHCP4(gosigma.ModelVirtio)
273
274 if args.MachineConfig.Bootstrap {
275 cc.SetMeta(jujuMetaInstance, jujuMetaInstanceStateServer)
276 } else {
277 cc.SetMeta(jujuMetaInstance, jujuMetaInstanceServer)
278 }
279
280 if c.name != "" {
281 cc.SetMeta(jujuMetaEnvironment, c.name)
282 }
283
284 if srv, err = c.conn.CreateServer(cc); err != nil {
285 err = fmt.Errorf("error creating new instance: %v", err)
286 return
287 }
288
289 if err = srv.StartWait(); err != nil {
290 err = fmt.Errorf("error booting new instance: %v", err)
291 return
292 }
293
294 var ipaddr string
295 instanceNetworkAvailable := func(s gosigma.Server) bool {
296 ipaddr = sigmaInstance{s}.findIPv4()
297 return ipaddr != ""
298 }
299 if err = srv.Wait(instanceNetworkAvailable); err != nil {
300 err = fmt.Errorf("error waiting for instance IP address: %v", err)
301 return
302 }
303
304 logger.Tracef("instance ip %q", ipaddr)
305
306 return
307}
0308
=== added file 'provider/cloudsigma/client_test.go'
--- provider/cloudsigma/client_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/client_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,316 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8 "strings"
9 "time"
10
11 "github.com/Altoros/gosigma"
12 "github.com/Altoros/gosigma/data"
13 "github.com/Altoros/gosigma/mock"
14 "github.com/juju/loggo"
15 gc "launchpad.net/gocheck"
16 "launchpad.net/juju-core/constraints"
17 "launchpad.net/juju-core/environs"
18 "launchpad.net/juju-core/environs/cloudinit"
19 "launchpad.net/juju-core/instance"
20 "launchpad.net/juju-core/testing"
21 "launchpad.net/juju-core/tools"
22 "launchpad.net/juju-core/version"
23)
24
25type clientSuite struct {
26 testing.BaseSuite
27}
28
29var _ = gc.Suite(&clientSuite{})
30
31func (s *clientSuite) SetUpSuite(c *gc.C) {
32 s.BaseSuite.SetUpSuite(c)
33 mock.Start()
34}
35
36func (s *clientSuite) TearDownSuite(c *gc.C) {
37 mock.Stop()
38 s.BaseSuite.TearDownSuite(c)
39}
40
41func (s *clientSuite) SetUpTest(c *gc.C) {
42 s.BaseSuite.SetUpTest(c)
43
44 ll := logger.LogLevel()
45 logger.SetLogLevel(loggo.TRACE)
46 s.AddCleanup(func(*gc.C) { logger.SetLogLevel(ll) })
47
48 mock.Reset()
49}
50
51func (s *clientSuite) TearDownTest(c *gc.C) {
52 mock.Reset()
53 s.BaseSuite.TearDownTest(c)
54}
55
56func testNewClient(c *gc.C, endpoint, username, password string) (*environClient, error) {
57 ecfg := &environConfig{
58 Config: newConfig(c, testing.Attrs{"name": "client-test"}),
59 attrs: map[string]interface{}{
60 "region": endpoint,
61 "username": username,
62 "password": password,
63 },
64 }
65 return newClient(ecfg)
66}
67
68func (s *clientSuite) TestClientNew(c *gc.C) {
69 cli, err := testNewClient(c, "https://testing.invalid", "user", "password")
70 c.Check(err, gc.IsNil)
71 c.Check(cli, gc.NotNil)
72
73 cli, err = testNewClient(c, "http://testing.invalid", "user", "password")
74 c.Check(err, gc.ErrorMatches, "endpoint must use https scheme")
75 c.Check(cli, gc.IsNil)
76
77 cli, err = testNewClient(c, "https://testing.invalid", "", "password")
78 c.Check(err, gc.ErrorMatches, "username is not allowed to be empty")
79 c.Check(cli, gc.IsNil)
80}
81
82func (s *clientSuite) TestClientConfigChanged(c *gc.C) {
83 ecfg := &environConfig{
84 Config: newConfig(c, testing.Attrs{"name": "client-test"}),
85 attrs: map[string]interface{}{
86 "region": "https://testing.invalid",
87 "username": "user",
88 "password": "password",
89 },
90 }
91
92 cli, err := newClient(ecfg)
93 c.Check(err, gc.IsNil)
94 c.Check(cli, gc.NotNil)
95
96 rc := cli.configChanged(ecfg)
97 c.Check(rc, gc.Equals, false)
98
99 ecfg.attrs["region"] = ""
100 rc = cli.configChanged(ecfg)
101 c.Check(rc, gc.Equals, true)
102
103 ecfg.attrs["region"] = "https://testing.invalid"
104 ecfg.attrs["username"] = "user1"
105 rc = cli.configChanged(ecfg)
106 c.Check(rc, gc.Equals, true)
107
108 ecfg.attrs["username"] = "user"
109 ecfg.attrs["password"] = "password1"
110 rc = cli.configChanged(ecfg)
111 c.Check(rc, gc.Equals, true)
112
113 ecfg.attrs["password"] = "password"
114 ecfg.Config = newConfig(c, testing.Attrs{"name": "changed"})
115 rc = cli.configChanged(ecfg)
116 c.Check(rc, gc.Equals, true)
117}
118
119func addTestClientServer(c *gc.C, instance, env, ip string) string {
120 json := `{"meta": {`
121 if instance != "" {
122 json += fmt.Sprintf(`"juju-instance": "%s"`, instance)
123 if env != "" {
124 json += fmt.Sprintf(`, "juju-environment": "%s"`, env)
125 }
126 }
127 json += fmt.Sprintf(`}, "status": "running", "nics":[{
128 "runtime": {
129 "interface_type": "public",
130 "ip_v4": {
131 "resource_uri": "/api/2.0/ips/%s/",
132 "uuid": "%s"
133 }}}]}`, ip, ip)
134 r := strings.NewReader(json)
135 s, err := data.ReadServer(r)
136 c.Assert(err, gc.IsNil)
137 mock.AddServer(s)
138 return s.UUID
139}
140
141func (s *clientSuite) TestClientInstances(c *gc.C) {
142 addTestClientServer(c, "", "", "")
143 addTestClientServer(c, jujuMetaInstanceServer, "alien", "")
144 addTestClientServer(c, jujuMetaInstanceStateServer, "alien", "")
145 addTestClientServer(c, jujuMetaInstanceServer, "client-test", "1.1.1.1")
146 addTestClientServer(c, jujuMetaInstanceServer, "client-test", "2.2.2.2")
147 suuid := addTestClientServer(c, jujuMetaInstanceStateServer, "client-test", "3.3.3.3")
148
149 cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword)
150 c.Assert(err, gc.IsNil)
151
152 ss, err := cli.instances()
153 c.Assert(err, gc.IsNil)
154 c.Assert(ss, gc.NotNil)
155 c.Check(ss, gc.HasLen, 3)
156
157 sm, err := cli.instanceMap()
158 c.Assert(err, gc.IsNil)
159 c.Assert(sm, gc.NotNil)
160 c.Check(sm, gc.HasLen, 3)
161
162 uuid, ip, rc := cli.stateServerAddress()
163 c.Check(uuid, gc.Equals, suuid)
164 c.Check(ip, gc.Equals, "3.3.3.3")
165 c.Check(rc, gc.Equals, true)
166}
167
168func (s *clientSuite) TestClientStopStateInstance(c *gc.C) {
169 addTestClientServer(c, "", "", "")
170 addTestClientServer(c, jujuMetaInstanceServer, "alien", "")
171 addTestClientServer(c, jujuMetaInstanceStateServer, "alien", "")
172 addTestClientServer(c, jujuMetaInstanceServer, "client-test", "1.1.1.1")
173 addTestClientServer(c, jujuMetaInstanceServer, "client-test", "2.2.2.2")
174 suuid := addTestClientServer(c, jujuMetaInstanceStateServer, "client-test", "3.3.3.3")
175
176 cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword)
177 c.Assert(err, gc.IsNil)
178
179 cli.storage = &environStorage{uuid: suuid, tmp: true}
180
181 err = cli.stopInstance(instance.Id(suuid))
182 c.Assert(err, gc.IsNil)
183
184 uuid, ip, rc := cli.stateServerAddress()
185 c.Check(uuid, gc.Equals, "")
186 c.Check(ip, gc.Equals, "")
187 c.Check(rc, gc.Equals, false)
188
189 c.Check(cli.storage.tmp, gc.Equals, false)
190}
191
192func (s *clientSuite) TestClientInvalidStopInstance(c *gc.C) {
193 cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword)
194 c.Assert(err, gc.IsNil)
195
196 var id instance.Id
197 err = cli.stopInstance(id)
198 c.Check(err, gc.ErrorMatches, "invalid instance id")
199
200 err = cli.stopInstance("1234")
201 c.Check(err, gc.ErrorMatches, "404 Not Found.*")
202}
203
204func (s *clientSuite) TestClientInvalidServer(c *gc.C) {
205 cli, err := testNewClient(c, "https://testing.invalid", mock.TestUser, mock.TestPassword)
206 c.Assert(err, gc.IsNil)
207
208 cli.conn.ConnectTimeout(10 * time.Millisecond)
209
210 err = cli.stopInstance("1234")
211 c.Check(err, gc.ErrorMatches, "broken connection")
212
213 _, err = cli.instanceMap()
214 c.Check(err, gc.ErrorMatches, "broken connection")
215
216 uuid, ip, ok := cli.stateServerAddress()
217 c.Check(uuid, gc.Equals, "")
218 c.Check(ip, gc.Equals, "")
219 c.Check(ok, gc.Equals, false)
220}
221
222func (s *clientSuite) TestClientNewInstanceInvalidParams(c *gc.C) {
223 cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword)
224 c.Assert(err, gc.IsNil)
225
226 params := environs.StartInstanceParams{
227 Constraints: constraints.Value{},
228 }
229 server, drive, err := cli.newInstance(params)
230 c.Check(server, gc.IsNil)
231 c.Check(drive, gc.IsNil)
232 c.Check(err, gc.ErrorMatches, "invalid configuration for new instance")
233
234 params.MachineConfig = &cloudinit.MachineConfig{
235 Bootstrap: true,
236 Tools: &tools.Tools{
237 Version: version.Binary{
238 Series: "series",
239 },
240 },
241 }
242 server, drive, err = cli.newInstance(params)
243 c.Check(server, gc.IsNil)
244 c.Check(drive, gc.IsNil)
245 c.Check(err, gc.ErrorMatches, "series 'series' not supported")
246
247 var arch = "arch"
248 params.Constraints.Arch = &arch
249 server, drive, err = cli.newInstance(params)
250 c.Check(server, gc.IsNil)
251 c.Check(drive, gc.IsNil)
252 c.Check(err, gc.ErrorMatches, "arch 'arch' not supported")
253}
254
255func (s *clientSuite) TestClientNewInstanceInvalidTemplate(c *gc.C) {
256 cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword)
257 c.Assert(err, gc.IsNil)
258
259 params := environs.StartInstanceParams{
260 Constraints: constraints.Value{},
261 MachineConfig: &cloudinit.MachineConfig{
262 Bootstrap: true,
263 Tools: &tools.Tools{
264 Version: version.Binary{
265 Series: "trusty",
266 },
267 },
268 },
269 }
270 server, drive, err := cli.newInstance(params)
271 c.Check(server, gc.IsNil)
272 c.Check(drive, gc.IsNil)
273 c.Check(err, gc.ErrorMatches, "query drive template: 404 Not Found.*")
274}
275
276func (s *clientSuite) TestClientNewInstance(c *gc.C) {
277 cli, err := testNewClient(c, mock.Endpoint(""), mock.TestUser, mock.TestPassword)
278 c.Assert(err, gc.IsNil)
279
280 cli.conn.OperationTimeout(1 * time.Second)
281
282 params := environs.StartInstanceParams{
283 Constraints: constraints.Value{},
284 MachineConfig: &cloudinit.MachineConfig{
285 Bootstrap: true,
286 Tools: &tools.Tools{
287 Version: version.Binary{
288 Series: "trusty",
289 },
290 },
291 },
292 }
293 cs, err := newConstraints(params.MachineConfig.Bootstrap,
294 params.Constraints, params.MachineConfig.Tools.Version.Series)
295 c.Assert(cs, gc.NotNil)
296 c.Check(err, gc.IsNil)
297
298 templateDrive := &data.Drive{
299 Resource: data.Resource{URI: "uri", UUID: cs.driveTemplate},
300 LibraryDrive: data.LibraryDrive{
301 Arch: "arch",
302 ImageType: "image-type",
303 OS: "os",
304 Paid: true,
305 },
306 Size: 2200 * gosigma.Megabyte,
307 Status: "unmounted",
308 }
309 mock.ResetDrives()
310 mock.LibDrives.Add(templateDrive)
311
312 server, drive, err := cli.newInstance(params)
313 c.Check(server, gc.NotNil)
314 c.Check(drive, gc.NotNil)
315 c.Check(err, gc.IsNil)
316}
0317
=== added file 'provider/cloudsigma/config.go'
--- provider/cloudsigma/config.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/config.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,165 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8 "github.com/Altoros/gosigma"
9
10 "launchpad.net/juju-core/environs/config"
11 "launchpad.net/juju-core/schema"
12 "launchpad.net/juju-core/utils"
13)
14
15// boilerplateConfig will be shown in help output, so please keep it up to
16// date when you change environment configuration below.
17var boilerplateConfig = `# https://juju.ubuntu.com/docs/config-cloudsigma.html
18cloudsigma:
19 type: cloudsigma
20
21 # region holds the cloudsigma region (zrh, lvs, ...).
22 #
23 # region: <your region>
24
25 # credentials for CloudSigma account
26 #
27 # username: <your username>
28 # password: <secret>
29
30 # storage-port specifes the TCP port that the
31 # bootstrap machine's Juju storage server will listen
32 # on. It defaults to ` + fmt.Sprint(defaultStoragePort) + `
33 #
34 # storage-port: ` + fmt.Sprint(defaultStoragePort) + `
35
36`
37
38const defaultStoragePort = 8040
39
40var configFields = schema.Fields{
41 "username": schema.String(),
42 "password": schema.String(),
43 "region": schema.String(),
44
45 "storage-port": schema.ForceInt(),
46 "storage-auth-key": schema.String(),
47}
48
49var configDefaultFields = schema.Defaults{
50 "username": "",
51 "password": "",
52 "region": gosigma.DefaultRegion,
53
54 "storage-port": defaultStoragePort,
55}
56
57var configSecretFields = []string{
58 "storage-auth-key",
59}
60
61var configImmutableFields = []string{
62 "storage-port",
63 "storage-auth-key",
64 "region",
65}
66
67func prepareConfig(cfg *config.Config) (*config.Config, error) {
68 // Turn an incomplete config into a valid one, if possible.
69 attrs := cfg.UnknownAttrs()
70
71 if _, ok := attrs["storage-auth-key"]; !ok {
72 uuid, err := utils.NewUUID()
73 if err != nil {
74 return nil, err
75 }
76 attrs["storage-auth-key"] = uuid.String()
77 }
78
79 return cfg.Apply(attrs)
80}
81
82func validateConfig(cfg *config.Config, old *environConfig) (*environConfig, error) {
83 // Check sanity of juju-level fields.
84 var oldCfg *config.Config
85 if old != nil {
86 oldCfg = old.Config
87 }
88 if err := config.Validate(cfg, oldCfg); err != nil {
89 return nil, err
90 }
91
92 // Extract validated provider-specific fields. All of configFields will be
93 // present in validated, and defaults will be inserted if necessary. If the
94 // schema you passed in doesn't quite express what you need, you can make
95 // whatever checks you need here, before continuing.
96 // In particular, if you want to extract (say) credentials from the user's
97 // shell environment variables, you'll need to allow missing values to pass
98 // through the schema by setting a value of schema.Omit in the configFields
99 // map, and then to set and check them at this point. These values *must* be
100 // stored in newAttrs: a Config will be generated on the user's machine only
101 // to begin with, and will subsequently be used on a different machine that
102 // will probably not have those variables set.
103 newAttrs, err := cfg.ValidateUnknownAttrs(configFields, configDefaultFields)
104 if err != nil {
105 return nil, err
106 }
107 for field := range configFields {
108 if newAttrs[field] == "" {
109 return nil, fmt.Errorf("%s: must not be empty", field)
110 }
111 }
112
113 // If an old config was supplied, check any immutable fields have not changed.
114 if old != nil {
115 for _, field := range configImmutableFields {
116 if old.attrs[field] != newAttrs[field] {
117 return nil, fmt.Errorf(
118 "%s: cannot change from %v to %v",
119 field, old.attrs[field], newAttrs[field],
120 )
121 }
122 }
123 }
124
125 // Merge the validated provider-specific fields into the original config,
126 // to ensure the object we return is internally consistent.
127 newCfg, err := cfg.Apply(newAttrs)
128 if err != nil {
129 return nil, err
130 }
131 ecfg := &environConfig{
132 Config: newCfg,
133 attrs: newAttrs,
134 }
135
136 return ecfg, nil
137}
138
139type environConfig struct {
140 *config.Config
141 attrs map[string]interface{}
142}
143
144func (c environConfig) region() string {
145 return c.attrs["region"].(string)
146}
147
148func (c environConfig) username() string {
149 return c.attrs["username"].(string)
150}
151
152func (c environConfig) password() string {
153 return c.attrs["password"].(string)
154}
155
156func (c environConfig) storagePort() int {
157 return c.attrs["storage-port"].(int)
158}
159
160func (c environConfig) storageAuthKey() string {
161 if v, ok := c.attrs["storage-auth-key"].(string); ok {
162 return v
163 }
164 return ""
165}
0166
=== added file 'provider/cloudsigma/config_test.go'
--- provider/cloudsigma/config_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/config_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,329 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "crypto/rand"
8 "fmt"
9 gc "launchpad.net/gocheck"
10
11 "launchpad.net/juju-core/environs"
12 "launchpad.net/juju-core/environs/config"
13 "launchpad.net/juju-core/schema"
14 "launchpad.net/juju-core/testing"
15)
16
17func newConfig(c *gc.C, attrs testing.Attrs) *config.Config {
18 attrs = testing.FakeConfig().Merge(attrs)
19 cfg, err := config.New(config.UseDefaults, attrs)
20 c.Assert(err, gc.IsNil)
21 return cfg
22}
23
24func validAttrs() testing.Attrs {
25 return testing.FakeConfig().Merge(testing.Attrs{
26 "type": "cloudsigma",
27 "username": "user",
28 "password": "password",
29 "region": "zrh",
30 "storage-port": 8040,
31 "storage-auth-key": "ABCDEFGH",
32 })
33}
34
35type configSuite struct {
36 testing.BaseSuite
37}
38
39func (s *configSuite) SetUpSuite(c *gc.C) {
40 s.BaseSuite.SetUpSuite(c)
41}
42
43func (s *configSuite) SetUpTest(c *gc.C) {
44 s.BaseSuite.SetUpTest(c)
45 // speed up tests, do not create heavy stuff inside providers created withing this test suite
46 s.PatchValue(&newClient, func(cfg *environConfig) (*environClient, error) {
47 return nil, nil
48 })
49 s.PatchValue(&newStorage, func(ecfg *environConfig, client *environClient) (*environStorage, error) {
50 return nil, nil
51 })
52}
53
54var _ = gc.Suite(&configSuite{})
55
56func (s *configSuite) TestNewEnvironConfig(c *gc.C) {
57
58 type checker struct {
59 checker gc.Checker
60 value interface{}
61 }
62
63 var newConfigTests = []struct {
64 info string
65 insert testing.Attrs
66 remove []string
67 expect testing.Attrs
68 err string
69 }{{
70 info: "username is required",
71 remove: []string{"username"},
72 err: "username: must not be empty",
73 }, {
74 info: "username cannot be empty",
75 insert: testing.Attrs{"username": ""},
76 err: "username: must not be empty",
77 }, {
78 info: "password is required",
79 remove: []string{"password"},
80 err: "password: must not be empty",
81 }, {
82 info: "password cannot be empty",
83 insert: testing.Attrs{"password": ""},
84 err: "password: must not be empty",
85 }, {
86 info: "region is inserted if missing",
87 remove: []string{"region"},
88 expect: testing.Attrs{"region": "zrh"},
89 }, {
90 info: "region must not be empty",
91 insert: testing.Attrs{"region": ""},
92 err: "region: must not be empty",
93 }, {
94 info: "storage-port is inserted if missing",
95 remove: []string{"storage-port"},
96 expect: testing.Attrs{"storage-port": 8040},
97 }, {
98 info: "storage-port must be number",
99 insert: testing.Attrs{"storage-port": "abcd"},
100 err: "storage-port: expected number, got string\\(\"abcd\"\\)",
101 }, {
102 info: "storage-auth-key is inserted if missing",
103 remove: []string{"storage-auth-key"},
104 expect: testing.Attrs{"storage-auth-key": checker{gc.HasLen, 36}},
105 }, {
106 info: "storage-auth-key must not be empty",
107 insert: testing.Attrs{"storage-auth-key": ""},
108 err: "storage-auth-key: must not be empty",
109 }}
110
111 for i, test := range newConfigTests {
112 c.Logf("test %d: %s", i, test.info)
113 attrs := validAttrs().Merge(test.insert).Delete(test.remove...)
114 testConfig := newConfig(c, attrs)
115 environ, err := environs.New(testConfig)
116 if test.err == "" {
117 c.Check(err, gc.IsNil)
118 attrs := environ.Config().AllAttrs()
119 for field, value := range test.expect {
120 if chk, ok := value.(checker); ok {
121 c.Check(attrs[field], chk.checker, chk.value)
122 } else {
123 c.Check(attrs[field], gc.Equals, value)
124 }
125 }
126 } else {
127 c.Check(environ, gc.IsNil)
128 c.Check(err, gc.ErrorMatches, test.err)
129 }
130 }
131}
132
133var changeConfigTests = []struct {
134 info string
135 insert testing.Attrs
136 remove []string
137 expect testing.Attrs
138 err string
139}{{
140 info: "no change, no error",
141 expect: validAttrs(),
142}, {
143 info: "can change username",
144 insert: testing.Attrs{"username": "cloudsigma_user"},
145 expect: testing.Attrs{"username": "cloudsigma_user"},
146}, {
147 info: "can not change username to empty",
148 insert: testing.Attrs{"username": ""},
149 err: "username: must not be empty",
150}, {
151 info: "can change password",
152 insert: testing.Attrs{"password": "cloudsigma_password"},
153 expect: testing.Attrs{"password": "cloudsigma_password"},
154}, {
155 info: "can not change password to empty",
156 insert: testing.Attrs{"password": ""},
157 err: "password: must not be empty",
158}, {
159 info: "can change region",
160 insert: testing.Attrs{"region": "lvs"},
161 err: "region: cannot change from .* to .*",
162}, {
163 info: "can not change storage-port",
164 insert: testing.Attrs{"storage-port": 0},
165 err: "storage-port: cannot change from .* to .*",
166}, {
167 info: "can not change storage-auth-key",
168 insert: testing.Attrs{"storage-auth-key": "xxx"},
169 err: "storage-auth-key: cannot change from .* to .*",
170}}
171
172func (s *configSuite) TestValidateChange(c *gc.C) {
173
174 baseConfig := newConfig(c, validAttrs())
175 for i, test := range changeConfigTests {
176 c.Logf("test %d: %s", i, test.info)
177 attrs := validAttrs().Merge(test.insert).Delete(test.remove...)
178 testConfig := newConfig(c, attrs)
179 validatedConfig, err := providerInstance.Validate(testConfig, baseConfig)
180 if test.err == "" {
181 c.Check(err, gc.IsNil)
182 attrs := validatedConfig.AllAttrs()
183 for field, value := range test.expect {
184 c.Check(attrs[field], gc.Equals, value)
185 }
186 } else {
187 c.Check(validatedConfig, gc.IsNil)
188 c.Check(err, gc.ErrorMatches, "invalid config.*: "+test.err)
189 }
190
191 // reverse change
192 validatedConfig, err = providerInstance.Validate(baseConfig, testConfig)
193 if test.err == "" {
194 c.Check(err, gc.IsNil)
195 attrs := validatedConfig.AllAttrs()
196 for field, value := range validAttrs() {
197 c.Check(attrs[field], gc.Equals, value)
198 }
199 } else {
200 c.Check(validatedConfig, gc.IsNil)
201 c.Check(err, gc.ErrorMatches, "invalid .*config.*: "+test.err)
202 }
203 }
204}
205
206func (s *configSuite) TestSetConfig(c *gc.C) {
207 baseConfig := newConfig(c, validAttrs())
208 for i, test := range changeConfigTests {
209 c.Logf("test %d: %s", i, test.info)
210 environ, err := environs.New(baseConfig)
211 c.Assert(err, gc.IsNil)
212 attrs := validAttrs().Merge(test.insert).Delete(test.remove...)
213 testConfig := newConfig(c, attrs)
214 err = environ.SetConfig(testConfig)
215 newAttrs := environ.Config().AllAttrs()
216 if test.err == "" {
217 c.Check(err, gc.IsNil)
218 for field, value := range test.expect {
219 c.Check(newAttrs[field], gc.Equals, value)
220 }
221 } else {
222 c.Check(err, gc.ErrorMatches, test.err)
223 for field, value := range baseConfig.UnknownAttrs() {
224 c.Check(newAttrs[field], gc.Equals, value)
225 }
226 }
227 }
228}
229
230func (s *configSuite) TestConfigName(c *gc.C) {
231 baseConfig := newConfig(c, validAttrs().Merge(testing.Attrs{"name": "testname"}))
232 environ, err := environs.New(baseConfig)
233 c.Assert(err, gc.IsNil)
234 c.Check(environ.Name(), gc.Equals, "testname")
235}
236
237func (s *configSuite) TestBadUUIDGenerator(c *gc.C) {
238 fail := failReader{fmt.Errorf("error")}
239 s.PatchValue(&rand.Reader, &fail)
240
241 attrs := validAttrs().Delete("storage-auth-key")
242 testConfig := newConfig(c, attrs)
243 cfg, err := providerInstance.Prepare(nil, testConfig)
244
245 c.Check(cfg, gc.IsNil)
246 c.Check(err, gc.Equals, fail.err)
247}
248
249func (s *configSuite) TestEnvironConfig(c *gc.C) {
250 testConfig := newConfig(c, validAttrs())
251 ecfg, err := validateConfig(testConfig, nil)
252 c.Assert(ecfg, gc.NotNil)
253 c.Assert(err, gc.IsNil)
254 c.Check(ecfg.username(), gc.Equals, "user")
255 c.Check(ecfg.password(), gc.Equals, "password")
256 c.Check(ecfg.region(), gc.Equals, "zrh")
257 c.Check(ecfg.storagePort(), gc.Equals, 8040)
258 c.Check(ecfg.storageAuthKey(), gc.Equals, "ABCDEFGH")
259}
260
261func (s *configSuite) TestInvalidConfigChange(c *gc.C) {
262 oldAttrs := validAttrs().Merge(testing.Attrs{"name": "123"})
263 oldConfig := newConfig(c, oldAttrs)
264 newAttrs := validAttrs().Merge(testing.Attrs{"name": "321"})
265 newConfig := newConfig(c, newAttrs)
266
267 oldecfg, _ := providerInstance.Validate(oldConfig, nil)
268 c.Assert(oldecfg, gc.NotNil)
269
270 newecfg, err := providerInstance.Validate(newConfig, oldecfg)
271 c.Assert(newecfg, gc.IsNil)
272 c.Assert(err, gc.NotNil)
273}
274
275var secretAttrsConfigTests = []struct {
276 info string
277 insert testing.Attrs
278 remove []string
279 expect map[string]string
280 err string
281}{{
282 info: "no change, no error",
283 expect: map[string]string{"storage-auth-key": "ABCDEFGH"},
284}, {
285 info: "invalid config",
286 insert: testing.Attrs{"username": ""},
287 err: ".* must not be empty.*",
288}}
289
290func (s *configSuite) TestSecretAttrs(c *gc.C) {
291 for i, test := range secretAttrsConfigTests {
292 c.Logf("test %d: %s", i, test.info)
293 attrs := validAttrs().Merge(test.insert).Delete(test.remove...)
294 testConfig := newConfig(c, attrs)
295 sa, err := providerInstance.SecretAttrs(testConfig)
296 if test.err == "" {
297 c.Check(sa, gc.HasLen, len(test.expect))
298 for field, value := range test.expect {
299 c.Check(sa[field], gc.Equals, value)
300 }
301 c.Check(err, gc.IsNil)
302 } else {
303 c.Check(sa, gc.IsNil)
304 c.Check(err, gc.ErrorMatches, test.err)
305 }
306 }
307}
308
309func (s *configSuite) TestSecretAttrsAreStrings(c *gc.C) {
310 for i, field := range configSecretFields {
311 c.Logf("test %d: %s", i, field)
312 attrs := validAttrs().Merge(testing.Attrs{field: 0})
313
314 if v, ok := configFields[field]; ok {
315 configFields[field] = schema.ForceInt()
316 defer func(c schema.Checker) {
317 configFields[field] = c
318 }(v)
319 } else {
320 c.Errorf("secrect field %s not found in configFields", field)
321 continue
322 }
323
324 testConfig := newConfig(c, attrs)
325 sa, err := providerInstance.SecretAttrs(testConfig)
326 c.Check(sa, gc.IsNil)
327 c.Check(err, gc.ErrorMatches, "secret .* field must have a string value; got .*")
328 }
329}
0330
=== added file 'provider/cloudsigma/constraints.go'
--- provider/cloudsigma/constraints.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/constraints.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,95 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8
9 "github.com/Altoros/gosigma"
10 "launchpad.net/juju-core/constraints"
11)
12
13// This file contains implementation of CloudSigma instance constraints
14type sigmaConstraints struct {
15 driveTemplate string
16 driveSize uint64
17 cores uint64
18 power uint64
19 mem uint64
20}
21
22const defaultCPUPower = 2000
23const driveUbuntuTrusty64 = "473adb38-3b64-43b2-93bd-f1a3443c19ea"
24
25// newConstraints creates new CloudSigma constraints from juju common constraints
26func newConstraints(bootstrap bool, jc constraints.Value, series string) (*sigmaConstraints, error) {
27 var sc sigmaConstraints
28
29 if a := jc.Arch; a != nil {
30 switch *a {
31 case "amd64":
32 switch series {
33 case "trusty":
34 sc.driveTemplate = driveUbuntuTrusty64
35 default:
36 return nil, fmt.Errorf("series '%s' not supported", series)
37 }
38 default:
39 return nil, fmt.Errorf("arch '%s' not supported", *a)
40 }
41 } else {
42 switch series {
43 case "trusty":
44 sc.driveTemplate = driveUbuntuTrusty64
45 default:
46 return nil, fmt.Errorf("series '%s' not supported", series)
47 }
48 }
49
50 if size := jc.RootDisk; bootstrap && size == nil {
51 sc.driveSize = 5 * gosigma.Gigabyte
52 } else if size != nil {
53 sc.driveSize = *size * gosigma.Megabyte
54 }
55
56 if c := jc.CpuCores; c != nil {
57 sc.cores = *c
58 } else {
59 sc.cores = 1
60 }
61
62 if p := jc.CpuPower; p != nil {
63 sc.power = *p
64 } else {
65 if sc.cores == 1 {
66 // The default of cpu power is 2000 Mhz
67 sc.power = defaultCPUPower
68 } else {
69 // The maximum amount of cpu per smp is 2300
70 sc.power = sc.cores * defaultCPUPower
71 }
72 }
73
74 if m := jc.Mem; m != nil {
75 sc.mem = *m * gosigma.Megabyte
76 } else {
77 sc.mem = 2 * gosigma.Gigabyte
78 }
79
80 return &sc, nil
81}
82
83func (c *sigmaConstraints) String() string {
84 s := fmt.Sprintf("template=%s,drive=%dG", c.driveTemplate, c.driveSize/gosigma.Gigabyte)
85 if c.cores > 0 {
86 s += fmt.Sprintf(",cores=%d", c.cores)
87 }
88 if c.power > 0 {
89 s += fmt.Sprintf(",power=%d", c.power)
90 }
91 if c.mem > 0 {
92 s += fmt.Sprintf(",mem=%dG", c.mem/gosigma.Gigabyte)
93 }
94 return s
95}
096
=== added file 'provider/cloudsigma/constraints_test.go'
--- provider/cloudsigma/constraints_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/constraints_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,136 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "github.com/Altoros/gosigma"
8 gc "launchpad.net/gocheck"
9 "launchpad.net/juju-core/constraints"
10 "launchpad.net/juju-core/testing"
11)
12
13type constraintsSuite struct {
14 testing.BaseSuite
15}
16
17var _ = gc.Suite(&constraintsSuite{})
18
19type strv struct{ v string }
20type uint64v struct{ v uint64 }
21
22var defConstraints = map[string]sigmaConstraints{
23 "bootstrap-trusty": sigmaConstraints{
24 driveTemplate: driveUbuntuTrusty64,
25 driveSize: 5 * gosigma.Gigabyte,
26 cores: 1,
27 power: 2000,
28 mem: 2 * gosigma.Gigabyte,
29 },
30 "trusty": sigmaConstraints{
31 driveTemplate: driveUbuntuTrusty64,
32 driveSize: 0,
33 cores: 1,
34 power: 2000,
35 mem: 2 * gosigma.Gigabyte,
36 },
37 "trusty-c2-p4000": sigmaConstraints{
38 driveTemplate: driveUbuntuTrusty64,
39 driveSize: 0,
40 cores: 2,
41 power: 4000,
42 mem: 2 * gosigma.Gigabyte,
43 },
44}
45
46var newConstraintTests = []struct {
47 bootstrap bool
48 arch *strv
49 cores *uint64v
50 power *uint64v
51 mem *uint64v
52 disk *uint64v
53 series string
54 expected sigmaConstraints
55 err *strv
56}{
57 {true, nil, nil, nil, nil, nil, "trusty", defConstraints["bootstrap-trusty"], nil},
58 {false, nil, nil, nil, nil, nil, "trusty", defConstraints["trusty"], nil},
59 {true, nil, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}},
60 {false, nil, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}},
61 {true, &strv{"amd64"}, nil, nil, nil, nil, "trusty", defConstraints["bootstrap-trusty"], nil},
62 {false, &strv{"amd64"}, nil, nil, nil, nil, "trusty", defConstraints["trusty"], nil},
63 {true, &strv{"amd64"}, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}},
64 {false, &strv{"amd64"}, nil, nil, nil, nil, "", sigmaConstraints{}, &strv{"series '.*' not supported"}},
65 {true, nil, &uint64v{1}, nil, nil, nil, "trusty", defConstraints["bootstrap-trusty"], nil},
66 {false, nil, &uint64v{1}, nil, nil, nil, "trusty", defConstraints["trusty"], nil},
67 {false, nil, &uint64v{2}, nil, nil, nil, "trusty", defConstraints["trusty-c2-p4000"], nil},
68 {false, nil, &uint64v{2}, &uint64v{4000}, nil, nil, "trusty", defConstraints["trusty-c2-p4000"], nil},
69 {false, nil, nil, nil, &uint64v{2 * 1024}, nil, "trusty", defConstraints["trusty"], nil},
70 {false, nil, nil, nil, nil, &uint64v{5 * 1024}, "trusty", defConstraints["bootstrap-trusty"], nil},
71}
72
73func (s *constraintsSuite) TestConstraints(c *gc.C) {
74 for i, t := range newConstraintTests {
75 var cv constraints.Value
76 if t.arch != nil {
77 cv.Arch = &t.arch.v
78 }
79 if t.cores != nil {
80 cv.CpuCores = &t.cores.v
81 }
82 if t.power != nil {
83 cv.CpuPower = &t.power.v
84 }
85 if t.mem != nil {
86 cv.Mem = &t.mem.v
87 }
88 if t.disk != nil {
89 cv.RootDisk = &t.disk.v
90 }
91 v, err := newConstraints(t.bootstrap, cv, t.series)
92 if t.err == nil {
93 if !c.Check(*v, gc.Equals, t.expected) {
94 c.Logf("test (%d): %+v", i, t)
95 }
96 } else {
97 if !c.Check(err, gc.ErrorMatches, t.err.v) {
98 c.Logf("test (%d): %+v", i, t)
99 }
100 }
101 }
102}
103
104func (s *constraintsSuite) TestConstraintsArch(c *gc.C) {
105 var cv constraints.Value
106 var expected = sigmaConstraints{
107 driveTemplate: driveUbuntuTrusty64,
108 driveSize: 5 * gosigma.Gigabyte,
109 cores: 1,
110 power: 2000,
111 mem: 2 * gosigma.Gigabyte,
112 }
113
114 sc, err := newConstraints(true, cv, "trusty")
115 c.Check(err, gc.IsNil)
116 c.Check(*sc, gc.Equals, expected)
117
118 sc, err = newConstraints(true, cv, "")
119 c.Check(err, gc.ErrorMatches, "series '.*' not supported")
120 c.Check(sc, gc.IsNil)
121
122 arch := "amd64"
123 cv.Arch = &arch
124 sc, err = newConstraints(true, cv, "trusty")
125 c.Check(err, gc.IsNil)
126 c.Check(*sc, gc.Equals, expected)
127
128 sc, err = newConstraints(true, cv, "")
129 c.Check(err, gc.ErrorMatches, "series '.*' not supported")
130 c.Check(sc, gc.IsNil)
131
132 arch = ""
133 sc, err = newConstraints(true, cv, "")
134 c.Check(err, gc.ErrorMatches, "arch '.*' not supported")
135 c.Check(sc, gc.IsNil)
136}
0137
=== added file 'provider/cloudsigma/environ.go'
--- provider/cloudsigma/environ.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/environ.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,164 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "sync"
8
9 "github.com/Altoros/gosigma"
10 "launchpad.net/juju-core/constraints"
11 "launchpad.net/juju-core/environs"
12 "launchpad.net/juju-core/environs/config"
13 "launchpad.net/juju-core/environs/simplestreams"
14 "launchpad.net/juju-core/environs/storage"
15 "launchpad.net/juju-core/environs/tools"
16 "launchpad.net/juju-core/provider/common"
17 "launchpad.net/juju-core/state"
18 "launchpad.net/juju-core/state/api"
19)
20
21// This file contains the core of the Environ implementation.
22type environ struct {
23 name string
24
25 lock sync.Mutex
26
27 ecfg *environConfig
28 client *environClient
29 storage *environStorage
30}
31
32var _ environs.Environ = (*environ)(nil)
33var _ tools.SupportsCustomSources = (*environ)(nil)
34var _ simplestreams.HasRegion = (*environ)(nil)
35
36// Name returns the Environ's name.
37func (env environ) Name() string {
38 return env.name
39}
40
41// Provider returns the EnvironProvider that created this Environ.
42func (environ) Provider() environs.EnvironProvider {
43 return providerInstance
44}
45
46// SetConfig updates the Environ's configuration.
47//
48// Calls to SetConfig do not affect the configuration of values previously obtained
49// from Storage.
50func (env *environ) SetConfig(cfg *config.Config) error {
51 env.lock.Lock()
52 defer env.lock.Unlock()
53
54 ecfg, err := validateConfig(cfg, env.ecfg)
55 if err != nil {
56 return err
57 }
58
59 if env.client == nil || env.client.configChanged(ecfg) {
60 client, err := newClient(ecfg)
61 if err != nil {
62 return err
63 }
64
65 storage, err := newStorage(ecfg, client)
66 if err != nil {
67 return err
68 }
69
70 env.client = client
71 env.storage = storage
72 }
73
74 env.ecfg = ecfg
75
76 return nil
77}
78
79// Config returns the configuration data with which the Environ was created.
80// Note that this is not necessarily current; the canonical location
81// for the configuration data is stored in the state.
82func (env environ) Config() *config.Config {
83 return env.ecfg.Config
84}
85
86// Storage returns storage specific to the environment.
87func (env environ) Storage() storage.Storage {
88 return env.storage
89}
90
91// Bootstrap initializes the state for the environment, possibly
92// starting one or more instances. If the configuration's
93// AdminSecret is non-empty, the administrator password on the
94// newly bootstrapped state will be set to a hash of it (see
95// utils.PasswordHash), When first connecting to the
96// environment via the juju package, the password hash will be
97// automatically replaced by the real password.
98//
99// The supplied constraints are used to choose the initial instance
100// specification, and will be stored in the new environment's state.
101//
102// Bootstrap is responsible for selecting the appropriate tools,
103// and setting the agent-version configuration attribute prior to
104// bootstrapping the environment.
105func (env *environ) Bootstrap(ctx environs.BootstrapContext, params environs.BootstrapParams) error {
106 // You can probably ignore this method; the common implementation should work.
107 return common.Bootstrap(ctx, env, params)
108}
109
110// StateInfo returns information on the state initialized by Bootstrap.
111func (env *environ) StateInfo() (*state.Info, *api.Info, error) {
112 // You can probably ignore this method; the common implementation should work.
113 return common.StateInfo(env)
114}
115
116// Destroy shuts down all known machines and destroys the
117// rest of the environment. Note that on some providers,
118// very recently started instances may not be destroyed
119// because they are not yet visible.
120//
121// When Destroy has been called, any Environ referring to the
122// same remote environment may become invalid
123func (env *environ) Destroy() error {
124 // You can probably ignore this method; the common implementation should work.
125 return common.Destroy(env)
126}
127
128// PrecheckInstance performs a preflight check on the specified
129// series and constraints, ensuring that they are possibly valid for
130// creating an instance in this environment.
131//
132// PrecheckInstance is best effort, and not guaranteed to eliminate
133// all invalid parameters. If PrecheckInstance returns nil, it is not
134// guaranteed that the constraints are valid; if a non-nil error is
135// returned, then the constraints are definitely invalid.
136func (env environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
137 logger.Infof("cloudsigma:environ:PrecheckInstance")
138 return nil
139}
140
141// GetToolsSources returns a list of sources which are
142// used to search for simplestreams tools metadata.
143func (env environ) GetToolsSources() ([]simplestreams.DataSource, error) {
144 // Add the simplestreams source off private storage.
145 return []simplestreams.DataSource{
146 storage.NewStorageSimpleStreamsDataSource("cloud storage", env.Storage(), storage.BaseToolsPath),
147 }, nil
148}
149
150// Region is specified in the HasRegion interface.
151func (env environ) Region() (simplestreams.CloudSpec, error) {
152 return env.cloudSpec(env.ecfg.region())
153}
154
155func (env environ) cloudSpec(region string) (simplestreams.CloudSpec, error) {
156 endpoint, err := gosigma.ResolveEndpoint(region)
157 if err != nil {
158 return simplestreams.CloudSpec{}, err
159 }
160 return simplestreams.CloudSpec{
161 Region: region,
162 Endpoint: endpoint,
163 }, nil
164}
0165
=== added file 'provider/cloudsigma/environ_test.go'
--- provider/cloudsigma/environ_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/environ_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,96 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 gc "launchpad.net/gocheck"
8 "launchpad.net/juju-core/constraints"
9 "launchpad.net/juju-core/environs"
10 "launchpad.net/juju-core/environs/simplestreams"
11 "launchpad.net/juju-core/environs/tools"
12 "launchpad.net/juju-core/juju/arch"
13 "launchpad.net/juju-core/testing"
14)
15
16type environSuite struct {
17 testing.BaseSuite
18}
19
20var _ = gc.Suite(&environSuite{})
21
22func (s *environSuite) SetUpSuite(c *gc.C) {
23 s.BaseSuite.SetUpSuite(c)
24}
25
26func (s *environSuite) TearDownSuite(c *gc.C) {
27 s.BaseSuite.TearDownSuite(c)
28}
29
30func (s *environSuite) SetUpTest(c *gc.C) {
31 s.BaseSuite.SetUpTest(c)
32}
33
34func (s *environSuite) TearDownTest(c *gc.C) {
35 s.BaseSuite.TearDownTest(c)
36}
37
38func (s *environSuite) TestBase(c *gc.C) {
39 var emptyStorage environStorage
40
41 s.PatchValue(&newClient, func(*environConfig) (*environClient, error) {
42 return nil, nil
43 })
44 s.PatchValue(&newStorage, func(*environConfig, *environClient) (*environStorage, error) {
45 return &emptyStorage, nil
46 })
47
48 baseConfig := newConfig(c, validAttrs().Merge(testing.Attrs{"name": "testname"}))
49 environ, err := environs.New(baseConfig)
50 c.Assert(err, gc.IsNil)
51
52 cfg := environ.Config()
53 c.Assert(cfg, gc.NotNil)
54 c.Check(cfg.Name(), gc.Equals, "testname")
55
56 c.Check(environ.Storage(), gc.Equals, &emptyStorage)
57
58 c.Check(environ.PrecheckInstance("", constraints.Value{}, ""), gc.IsNil)
59
60 customSource, ok := environ.(tools.SupportsCustomSources)
61 c.Check(ok, gc.Equals, true)
62 c.Assert(customSource, gc.NotNil)
63
64 src, err := customSource.GetToolsSources()
65 c.Check(src, gc.NotNil)
66 c.Check(err, gc.IsNil)
67
68 hasRegion, ok := environ.(simplestreams.HasRegion)
69 c.Check(ok, gc.Equals, true)
70 c.Assert(hasRegion, gc.NotNil)
71
72 cloudSpec, err := hasRegion.Region()
73 c.Check(err, gc.IsNil)
74 c.Check(cloudSpec.Region, gc.Not(gc.Equals), "")
75 c.Check(cloudSpec.Endpoint, gc.Not(gc.Equals), "")
76
77 archs, err := environ.SupportedArchitectures()
78 c.Check(err, gc.IsNil)
79 c.Assert(archs, gc.NotNil)
80 c.Assert(archs, gc.HasLen, 1)
81 c.Check(archs[0], gc.Equals, arch.AMD64)
82
83 validator, err := environ.ConstraintsValidator()
84 c.Check(validator, gc.NotNil)
85 c.Check(err, gc.IsNil)
86
87 c.Check(environ.SupportNetworks(), gc.Equals, false)
88 c.Check(environ.SupportsUnitPlacement(), gc.IsNil)
89
90 c.Check(environ.OpenPorts(nil), gc.IsNil)
91 c.Check(environ.ClosePorts(nil), gc.IsNil)
92
93 ports, err := environ.Ports()
94 c.Check(ports, gc.IsNil)
95 c.Check(err, gc.IsNil)
96}
097
=== added file 'provider/cloudsigma/environcaps.go'
--- provider/cloudsigma/environcaps.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/environcaps.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,50 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "launchpad.net/juju-core/constraints"
8 "launchpad.net/juju-core/juju/arch"
9)
10
11// SupportedArchitectures returns the image architectures which can
12// be hosted by this environment.
13func (env *environ) SupportedArchitectures() ([]string, error) {
14 return []string{arch.AMD64}, nil
15}
16
17var unsupportedConstraints = []string{
18 constraints.Container,
19 constraints.InstanceType,
20 constraints.Tags,
21}
22
23// ConstraintsValidator returns a Validator instance which
24// is used to validate and merge constraints.
25func (env *environ) ConstraintsValidator() (constraints.Validator, error) {
26 validator := constraints.NewValidator()
27 validator.RegisterUnsupported(unsupportedConstraints)
28 supportedArches, err := env.SupportedArchitectures()
29 if err != nil {
30 return nil, err
31 }
32 validator.RegisterVocabulary(constraints.Arch, supportedArches)
33 return validator, nil
34}
35
36// SupportNetworks returns whether the environment has support to
37// specify networks for services and machines.
38func (env *environ) SupportNetworks() bool {
39 logger.Debugf("environ:SupportedNetworks")
40 return false
41}
42
43// SupportsUnitAssignment returns an error which, if non-nil, indicates
44// that the environment does not support unit placement. If the environment
45// does not support unit placement, then machines may not be created
46// without units, and units cannot be placed explcitly.
47func (env *environ) SupportsUnitPlacement() error {
48 logger.Debugf("cloudsigma:environ:SupportsUnitPlacement not implemented")
49 return nil
50}
051
=== added file 'provider/cloudsigma/environfirewall.go'
--- provider/cloudsigma/environfirewall.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/environfirewall.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,32 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "launchpad.net/juju-core/instance"
8)
9
10// Implementing the methods below (to do something other than return nil) will
11// cause `juju expose` to work when the firewall-mode is "global". If you
12// implement one of them, you should implement them all.
13
14// OpenPorts opens the given ports for the whole environment.
15// Must only be used if the environment was setup with the FwGlobal firewall mode.
16func (env environ) OpenPorts(ports []instance.Port) error {
17 logger.Warningf("pretending to open ports %v for all instances", ports)
18 return nil
19}
20
21// ClosePorts closes the given ports for the whole environment.
22// Must only be used if the environment was setup with the FwGlobal firewall mode.
23func (env environ) ClosePorts(ports []instance.Port) error {
24 logger.Warningf("pretending to close ports %v for all instances", ports)
25 return nil
26}
27
28// Ports returns the ports opened for the whole environment.
29// Must only be used if the environment was setup with the FwGlobal firewall mode.
30func (env environ) Ports() ([]instance.Port, error) {
31 return nil, nil
32}
033
=== added file 'provider/cloudsigma/environinstance.go'
--- provider/cloudsigma/environinstance.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/environinstance.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,291 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8 "os"
9 "path"
10 "strings"
11 "time"
12
13 "github.com/Altoros/gosigma"
14 "github.com/juju/errors"
15 "github.com/juju/loggo"
16 "launchpad.net/juju-core/agent"
17 coreCloudinit "launchpad.net/juju-core/cloudinit"
18 "launchpad.net/juju-core/cloudinit/sshinit"
19 "launchpad.net/juju-core/environs"
20 "launchpad.net/juju-core/environs/cloudinit"
21 "launchpad.net/juju-core/environs/network"
22 "launchpad.net/juju-core/instance"
23 "launchpad.net/juju-core/juju/arch"
24 "launchpad.net/juju-core/utils"
25 "launchpad.net/juju-core/utils/ssh"
26 "launchpad.net/juju-core/worker/localstorage"
27)
28
29//
30// Imlementation of InstanceBroker: methods for starting and stopping instances.
31//
32
33// StartInstance asks for a new instance to be created, associated with
34// the provided config in machineConfig. The given config describes the juju
35// state for the new instance to connect to. The config MachineNonce, which must be
36// unique within an environment, is used by juju to protect against the
37// consequences of multiple instances being started with the same machine id.
38func (env environ) StartInstance(args environs.StartInstanceParams) (
39 instance.Instance, *instance.HardwareCharacteristics, []network.Info, error) {
40 logger.Infof("sigmaEnviron.StartInstance...")
41
42 if args.MachineConfig == nil {
43 return nil, nil, nil, fmt.Errorf("machine configuration is nil")
44 }
45
46 if args.MachineConfig.HasNetworks() {
47 return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet")
48 }
49
50 if len(args.Tools) == 0 {
51 return nil, nil, nil, fmt.Errorf("tools not found")
52 }
53
54 args.MachineConfig.Tools = args.Tools[0]
55 if err := environs.FinishMachineConfig(args.MachineConfig, env.Config(), args.Constraints); err != nil {
56 return nil, nil, nil, err
57 }
58
59 client := env.client
60 server, rootdrive, err := client.newInstance(args)
61 if err != nil {
62 return nil, nil, nil, fmt.Errorf("failed start instance: %v", err)
63 }
64
65 inst := sigmaInstance{server: server}
66 addr := inst.findIPv4()
67 if addr == "" {
68 return nil, nil, nil, fmt.Errorf("failed obtain instance IP address")
69 }
70
71 // prepare new instance: wait up and running, populate nonce file, etc
72 if err := env.prepareInstance(addr, args.MachineConfig); err != nil {
73 return nil, nil, nil, fmt.Errorf("failed prepare instanse: %v", err)
74 }
75
76 // provide additional agent config for localstorage, if any
77 if env.storage.tmp && args.MachineConfig.Bootstrap {
78 if err := env.prepareStorage(addr, args.MachineConfig); err != nil {
79 return nil, nil, nil, fmt.Errorf("failed prepare storage: %v", err)
80 }
81 }
82
83 // prepare hardware characteristics
84 hwch := inst.hardware()
85
86 // populate root drive hardware characteristics
87 switch rootdrive.Arch() {
88 case "64":
89 var a = arch.AMD64
90 hwch.Arch = &a
91 }
92
93 diskSpace := rootdrive.Size() / gosigma.Megabyte
94 if diskSpace > 0 {
95 hwch.RootDisk = &diskSpace
96 }
97
98 logger.Tracef("hardware: %v", hwch)
99
100 return inst, hwch, nil, nil
101}
102
103// AllInstances returns all instances currently known to the broker.
104func (env environ) AllInstances() ([]instance.Instance, error) {
105 // Please note that this must *not* return instances that have not been
106 // allocated as part of this environment -- if it does, juju will see they
107 // are not tracked in state, assume they're stale/rogue, and shut them down.
108
109 logger.Tracef("environ.AllInstances...")
110
111 servers, err := env.client.instances()
112 if err != nil {
113 logger.Tracef("environ.AllInstances failed: %v", err)
114 return nil, err
115 }
116
117 instances := make([]instance.Instance, 0, len(servers))
118 for _, server := range servers {
119 instance := sigmaInstance{server: server}
120 instances = append(instances, instance)
121 }
122
123 if logger.LogLevel() <= loggo.TRACE {
124 logger.Tracef("All instances, len = %d:", len(instances))
125 for _, instance := range instances {
126 logger.Tracef("... id: %q, status: %q", instance.Id(), instance.Status())
127 }
128 }
129
130 return instances, nil
131}
132
133// Instances returns a slice of instances corresponding to the
134// given instance ids. If no instances were found, but there
135// was no other error, it will return ErrNoInstances. If
136// some but not all the instances were found, the returned slice
137// will have some nil slots, and an ErrPartialInstances error
138// will be returned.
139func (env environ) Instances(ids []instance.Id) ([]instance.Instance, error) {
140 logger.Tracef("environ.Instances %#v", ids)
141 // Please note that this must *not* return instances that have not been
142 // allocated as part of this environment -- if it does, juju will see they
143 // are not tracked in state, assume they're stale/rogue, and shut them down.
144 // This advice applies even if an instance id passed in corresponds to a
145 // real instance that's not part of the environment -- the Environ should
146 // treat that no differently to a request for one that does not exist.
147
148 m, err := env.client.instanceMap()
149 if err != nil {
150 logger.Tracef("environ.Instances failed: %v", err)
151 return nil, err
152 }
153
154 var found int
155 r := make([]instance.Instance, len(ids))
156 for i, id := range ids {
157 if s, ok := m[string(id)]; ok {
158 r[i] = sigmaInstance{server: s}
159 found++
160 }
161 }
162
163 if found == 0 {
164 err = environs.ErrNoInstances
165 } else if found != len(ids) {
166 err = environs.ErrPartialInstances
167 }
168
169 return r, err
170}
171
172// StopInstances shuts down the given instances.
173func (env environ) StopInstances(instances ...instance.Id) error {
174 logger.Infof("stop instances %+v", instances)
175
176 var err error
177
178 for _, instance := range instances {
179 if e := env.client.stopInstance(instance); e != nil {
180 err = e
181 }
182 }
183
184 return err
185}
186
187func (env environ) prepareInstance(addr string, mcfg *cloudinit.MachineConfig) error {
188 host := "ubuntu@" + addr
189 logger.Debugf("Running prepare script on %s", host)
190
191 cmds := []string{"#!/bin/bash", "set -e"}
192 cmds = append(cmds, fmt.Sprintf("mkdir -p '%s'", mcfg.DataDir))
193
194 nonceFile := path.Join(mcfg.DataDir, cloudinit.NonceFile)
195 nonceContent := mcfg.MachineNonce
196 cmds = append(cmds, fmt.Sprintf("echo '%s' > %s", nonceContent, nonceFile))
197
198 script := strings.Join(cmds, "\n")
199
200 if !mcfg.Bootstrap {
201 cloudcfg := coreCloudinit.New()
202 if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil {
203 return err
204 }
205 configScript, err := sshinit.ConfigureScript(cloudcfg)
206 if err != nil {
207 return err
208 }
209
210 script += "\n\n"
211 script += configScript
212 }
213
214 logger.Tracef("Script:\n%s", script)
215
216 keyfile := path.Join(mcfg.DataDir, agent.SystemIdentity)
217 if _, err := os.Stat(keyfile); err != nil {
218 keyfile = ""
219 }
220 logger.Tracef("System identity file: %q", keyfile)
221
222 var retryAttempts = utils.AttemptStrategy{
223 Total: 120 * time.Second,
224 Delay: 300 * time.Millisecond,
225 }
226
227 var err error
228 for attempt := retryAttempts.Start(); attempt.Next(); {
229 var options ssh.Options
230 if keyfile != "" {
231 options.SetIdentities(keyfile)
232 }
233
234 cmd := ssh.Command(host, []string{"sudo", "/bin/bash"}, &options)
235 cmd.Stdin = strings.NewReader(script)
236
237 var logw = loggerWriter{logger.LogLevel()}
238 cmd.Stderr = &logw
239 cmd.Stdout = &logw
240
241 if err = cmd.Run(); err == nil {
242 break
243 }
244 }
245
246 if err != nil {
247 return fmt.Errorf("failed running prepare script: %v", err)
248 }
249
250 return nil
251}
252
253func (env environ) prepareStorage(addr string, mcfg *cloudinit.MachineConfig) error {
254 storagePort := env.ecfg.storagePort()
255 storageDir := mcfg.DataDir + "/" + storageSubdir
256
257 logger.Debugf("Moving local temporary storage to %s:%d (%s)...", addr, storagePort, storageDir)
258 if err := env.storage.MoveToSSH("ubuntu", addr); err != nil {
259 return err
260 }
261
262 if strings.Contains(mcfg.Tools.URL, "%s") {
263 mcfg.Tools.URL = fmt.Sprintf(mcfg.Tools.URL, "file://"+storageDir)
264 logger.Tracef("Tools URL patched to %q", mcfg.Tools.URL)
265 }
266
267 // prepare configuration for local storage at bootstrap host
268 storageConfig := storageConfig{
269 ecfg: env.ecfg,
270 storageDir: storageDir,
271 storageAddr: addr,
272 storagePort: storagePort,
273 }
274
275 agentEnv, err := localstorage.StoreConfig(&storageConfig)
276 if err != nil {
277 return err
278 }
279
280 for k, v := range agentEnv {
281 mcfg.AgentEnvironment[k] = v
282 }
283
284 return nil
285}
286
287// AllocateAddress requests a new address to be allocated for the
288// given instance on the given network.
289func (env environ) AllocateAddress(instID instance.Id, netID network.Id) (instance.Address, error) {
290 return instance.Address{}, errors.NotSupportedf("AllocateAddress")
291}
0292
=== added file 'provider/cloudsigma/environinstance_test.go'
--- provider/cloudsigma/environinstance_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/environinstance_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,206 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "time"
8
9 "github.com/Altoros/gosigma/mock"
10 "github.com/juju/loggo"
11 gc "launchpad.net/gocheck"
12 "launchpad.net/juju-core/environs"
13 "launchpad.net/juju-core/environs/cloudinit"
14 "launchpad.net/juju-core/environs/config"
15 "launchpad.net/juju-core/environs/network"
16 "launchpad.net/juju-core/instance"
17 "launchpad.net/juju-core/state/api"
18 "launchpad.net/juju-core/testing"
19 "launchpad.net/juju-core/tools"
20)
21
22type environInstanceSuite struct {
23 testing.BaseSuite
24 baseConfig *config.Config
25}
26
27var _ = gc.Suite(&environInstanceSuite{})
28
29func (s *environInstanceSuite) SetUpSuite(c *gc.C) {
30 s.BaseSuite.SetUpSuite(c)
31
32 mock.Start()
33
34 attrs := testing.Attrs{
35 "name": "testname",
36 "region": mock.Endpoint(""),
37 "username": mock.TestUser,
38 "password": mock.TestPassword,
39 }
40 s.baseConfig = newConfig(c, validAttrs().Merge(attrs))
41}
42
43func (s *environInstanceSuite) TearDownSuite(c *gc.C) {
44 mock.Stop()
45 s.BaseSuite.TearDownSuite(c)
46}
47
48func (s *environInstanceSuite) SetUpTest(c *gc.C) {
49 s.BaseSuite.SetUpTest(c)
50
51 ll := logger.LogLevel()
52 logger.SetLogLevel(loggo.TRACE)
53 s.AddCleanup(func(*gc.C) { logger.SetLogLevel(ll) })
54
55 mock.Reset()
56}
57
58func (s *environInstanceSuite) TearDownTest(c *gc.C) {
59 mock.Reset()
60 s.BaseSuite.TearDownTest(c)
61}
62
63func (s *environInstanceSuite) createEnviron(c *gc.C, cfg *config.Config) environs.Environ {
64 var emptyStorage environStorage
65 s.PatchValue(&newStorage, func(*environConfig, *environClient) (*environStorage, error) {
66 return &emptyStorage, nil
67 })
68 if cfg == nil {
69 cfg = s.baseConfig
70 }
71 environ, err := environs.New(cfg)
72 c.Assert(environ, gc.NotNil)
73 c.Assert(err, gc.IsNil)
74 return environ
75}
76
77func (s *environInstanceSuite) TestInstances(c *gc.C) {
78 environ := s.createEnviron(c, nil)
79
80 instances, err := environ.AllInstances()
81 c.Assert(instances, gc.NotNil)
82 c.Assert(err, gc.IsNil)
83 c.Check(instances, gc.HasLen, 0)
84
85 uuid0 := addTestClientServer(c, jujuMetaInstanceServer, "testname", "1.1.1.1")
86 uuid1 := addTestClientServer(c, jujuMetaInstanceStateServer, "testname", "2.2.2.2")
87 addTestClientServer(c, jujuMetaInstanceServer, "other-env", "0.1.1.1")
88 addTestClientServer(c, jujuMetaInstanceStateServer, "other-env", "0.2.2.2")
89
90 instances, err = environ.AllInstances()
91 c.Assert(instances, gc.NotNil)
92 c.Assert(err, gc.IsNil)
93 c.Check(instances, gc.HasLen, 2)
94
95 ids := []instance.Id{instance.Id(uuid0), instance.Id(uuid1)}
96 instances, err = environ.Instances(ids)
97 c.Assert(instances, gc.NotNil)
98 c.Assert(err, gc.IsNil)
99 c.Check(instances, gc.HasLen, 2)
100
101 ids = append(ids, instance.Id("fake-instance"))
102 instances, err = environ.Instances(ids)
103 c.Assert(instances, gc.NotNil)
104 c.Assert(err, gc.Equals, environs.ErrPartialInstances)
105 c.Check(instances, gc.HasLen, 3)
106 c.Check(instances[0], gc.NotNil)
107 c.Check(instances[1], gc.NotNil)
108 c.Check(instances[2], gc.IsNil)
109
110 err = environ.StopInstances(ids...)
111 c.Assert(err, gc.ErrorMatches, "404 Not Found.*")
112
113 instances, err = environ.Instances(ids)
114 c.Assert(instances, gc.NotNil)
115 c.Assert(err, gc.Equals, environs.ErrNoInstances)
116 c.Check(instances, gc.HasLen, 3)
117 c.Check(instances[0], gc.IsNil)
118 c.Check(instances[1], gc.IsNil)
119 c.Check(instances[2], gc.IsNil)
120}
121
122func (s *environInstanceSuite) TestInstancesFail(c *gc.C) {
123 attrs := testing.Attrs{
124 "name": "testname",
125 "region": "https://0.1.2.3:2000/api/2.0/",
126 "username": mock.TestUser,
127 "password": mock.TestPassword,
128 }
129 baseConfig := newConfig(c, validAttrs().Merge(attrs))
130
131 newClientFunc := newClient
132 s.PatchValue(&newClient, func(cfg *environConfig) (*environClient, error) {
133 cli, err := newClientFunc(cfg)
134 if cli != nil {
135 cli.conn.ConnectTimeout(10 * time.Millisecond)
136 }
137 return cli, err
138 })
139
140 environ := s.createEnviron(c, baseConfig)
141
142 instances, err := environ.AllInstances()
143 c.Assert(instances, gc.IsNil)
144 c.Assert(err, gc.NotNil)
145
146 instances, err = environ.Instances([]instance.Id{instance.Id("123"), instance.Id("321")})
147 c.Assert(instances, gc.IsNil)
148 c.Assert(err, gc.NotNil)
149}
150
151func (s *environInstanceSuite) TestAllocateAddress(c *gc.C) {
152 environ := s.createEnviron(c, nil)
153
154 addr, err := environ.AllocateAddress(instance.Id(""), network.Id(""))
155 c.Check(addr, gc.Equals, instance.Address{})
156 c.Check(err, gc.ErrorMatches, "AllocateAddress not supported")
157}
158
159func (s *environInstanceSuite) TestStartInstanceError(c *gc.C) {
160 environ := s.createEnviron(c, nil)
161
162 inst, hw, ni, err := environ.StartInstance(environs.StartInstanceParams{})
163 c.Check(inst, gc.IsNil)
164 c.Check(hw, gc.IsNil)
165 c.Check(ni, gc.IsNil)
166 c.Check(err, gc.ErrorMatches, "machine configuration is nil")
167
168 inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{
169 MachineConfig: &cloudinit.MachineConfig{
170 IncludeNetworks: []string{"value"},
171 },
172 })
173 c.Check(inst, gc.IsNil)
174 c.Check(hw, gc.IsNil)
175 c.Check(ni, gc.IsNil)
176 c.Check(err, gc.ErrorMatches, "starting instances with networks is not supported yet")
177
178 inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{
179 MachineConfig: &cloudinit.MachineConfig{},
180 })
181 c.Check(inst, gc.IsNil)
182 c.Check(hw, gc.IsNil)
183 c.Check(ni, gc.IsNil)
184 c.Check(err, gc.ErrorMatches, "tools not found")
185
186 inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{
187 Tools: tools.List{&tools.Tools{}},
188 MachineConfig: &cloudinit.MachineConfig{
189 Bootstrap: true,
190 APIInfo: &api.Info{},
191 },
192 })
193 c.Check(inst, gc.IsNil)
194 c.Check(hw, gc.IsNil)
195 c.Check(ni, gc.IsNil)
196 c.Check(err, gc.ErrorMatches, "cannot complete machine configuration:.*")
197
198 inst, hw, ni, err = environ.StartInstance(environs.StartInstanceParams{
199 Tools: tools.List{&tools.Tools{}},
200 MachineConfig: &cloudinit.MachineConfig{},
201 })
202 c.Check(inst, gc.IsNil)
203 c.Check(hw, gc.IsNil)
204 c.Check(ni, gc.IsNil)
205 c.Check(err, gc.ErrorMatches, "failed start instance:.*")
206}
0207
=== added file 'provider/cloudsigma/instance.go'
--- provider/cloudsigma/instance.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/instance.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,148 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8
9 "github.com/Altoros/gosigma"
10 "launchpad.net/juju-core/instance"
11 "launchpad.net/juju-core/provider/common"
12)
13
14type sigmaInstance struct {
15 server gosigma.Server
16}
17
18// Id returns a provider-generated identifier for the Instance.
19func (i sigmaInstance) Id() instance.Id {
20 if i.server == nil {
21 return instance.Id("")
22 }
23 id := instance.Id(i.server.UUID())
24 logger.Tracef("sigmaInstance.Id: %s", id)
25 return id
26}
27
28// Status returns the provider-specific status for the instance.
29func (i sigmaInstance) Status() string {
30 if i.server == nil {
31 return ""
32 }
33 status := i.server.Status()
34 logger.Tracef("sigmaInstance.Status: %s", status)
35 return status
36}
37
38// Refresh refreshes local knowledge of the instance from the provider.
39func (i sigmaInstance) Refresh() error {
40 if i.server == nil {
41 return fmt.Errorf("invalid instance")
42 }
43 err := i.server.Refresh()
44 logger.Tracef("sigmaInstance.Refresh: %s", err)
45 return err
46}
47
48// Addresses returns a list of hostnames or ip addresses
49// associated with the instance. This will supercede DNSName
50// which can be implemented by selecting a preferred address.
51func (i sigmaInstance) Addresses() ([]instance.Address, error) {
52
53 ip := i.findIPv4()
54
55 if ip == "" {
56 logger.Tracef("sigmaInstance.Addresses: IPv4 address not found")
57 return nil, instance.ErrNoDNSName
58 }
59
60 addr := instance.Address{
61 Value: ip,
62 Type: instance.Ipv4Address,
63 NetworkScope: instance.NetworkPublic,
64 }
65
66 logger.Tracef("sigmaInstance.Addresses: %v", addr)
67
68 return []instance.Address{addr}, nil
69}
70
71// DNSName returns the DNS name for the instance.
72// If the name is not yet allocated, it will return
73// an ErrNoDNSName error.
74func (i sigmaInstance) DNSName() (string, error) {
75 ip := i.findIPv4()
76
77 if ip == "" {
78 logger.Tracef("sigmaInstance.DNSName: IPv4 address not found, refreshing...")
79 if err := i.Refresh(); err != nil {
80 return "", err
81 }
82
83 ip = i.findIPv4()
84 if ip == "" {
85 return "", instance.ErrNoDNSName
86 }
87 }
88
89 logger.Infof("sigmaInstance.DNSName: %s", ip)
90
91 return ip, nil
92}
93
94// WaitDNSName returns the DNS name for the instance,
95// waiting until it is allocated if necessary.
96// TODO: We may not need this in the interface any more. All
97// implementations now delegate to environs.WaitDNSName.
98func (i sigmaInstance) WaitDNSName() (string, error) {
99 return common.WaitDNSName(i)
100}
101
102// OpenPorts opens the given ports on the instance, which
103// should have been started with the given machine id.
104func (i sigmaInstance) OpenPorts(machineID string, ports []instance.Port) error {
105 logger.Tracef("sigmaInstance.OpenPorts: not implemented")
106 return nil
107}
108
109// ClosePorts closes the given ports on the instance, which
110// should have been started with the given machine id.
111func (i sigmaInstance) ClosePorts(machineID string, ports []instance.Port) error {
112 logger.Tracef("sigmaInstance.ClosePorts: not implemented")
113 return nil
114}
115
116// Ports returns the set of ports open on the instance, which
117// should have been started with the given machine id.
118// The ports are returned as sorted by SortPorts.
119func (i sigmaInstance) Ports(machineID string) ([]instance.Port, error) {
120 logger.Tracef("sigmaInstance.Ports: not implemented")
121 return []instance.Port{}, nil
122}
123
124func (i sigmaInstance) findIPv4() string {
125 if i.server == nil {
126 return ""
127 }
128 addrs := i.server.IPv4()
129 if len(addrs) == 0 {
130 return ""
131 }
132 return addrs[0]
133}
134
135func (i sigmaInstance) hardware() *instance.HardwareCharacteristics {
136 if i.server == nil {
137 return nil
138 }
139 memory := i.server.Mem() / gosigma.Megabyte
140 cores := uint64(i.server.SMP())
141 cpu := i.server.CPU()
142 hw := instance.HardwareCharacteristics{
143 Mem: &memory,
144 CpuCores: &cores,
145 CpuPower: &cpu,
146 }
147 return &hw
148}
0149
=== added file 'provider/cloudsigma/instance_test.go'
--- provider/cloudsigma/instance_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/instance_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,328 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "strings"
8
9 "github.com/Altoros/gosigma"
10 "github.com/Altoros/gosigma/data"
11 "github.com/Altoros/gosigma/mock"
12 gc "launchpad.net/gocheck"
13 "launchpad.net/juju-core/instance"
14 "launchpad.net/juju-core/testing"
15)
16
17type instanceSuite struct {
18 testing.BaseSuite
19 inst *sigmaInstance
20 instWithoutIP *sigmaInstance
21}
22
23var _ = gc.Suite(&instanceSuite{})
24
25func (s *instanceSuite) SetUpSuite(c *gc.C) {
26 s.BaseSuite.SetUpSuite(c)
27 mock.Start()
28}
29
30func (s *instanceSuite) TearDownSuite(c *gc.C) {
31 mock.Stop()
32 s.BaseSuite.TearDownSuite(c)
33}
34
35func (s *instanceSuite) SetUpTest(c *gc.C) {
36 s.BaseSuite.SetUpTest(c)
37
38 cli, err := gosigma.NewClient(mock.Endpoint(""), mock.TestUser, mock.TestPassword, nil)
39 c.Assert(err, gc.IsNil)
40
41 mock.ResetServers()
42
43 ds, err := data.ReadServer(strings.NewReader(jsonInstanceData))
44 c.Assert(err, gc.IsNil)
45 mock.AddServer(ds)
46
47 mock.AddServer(&data.Server{
48 Resource: data.Resource{URI: "uri", UUID: "uuid-no-ip"},
49 })
50
51 server, err := cli.Server("f4ec5097-121e-44a7-a207-75bc02163260")
52 c.Assert(err, gc.IsNil)
53 c.Assert(server, gc.NotNil)
54 s.inst = &sigmaInstance{server}
55
56 server, err = cli.Server("uuid-no-ip")
57 c.Assert(err, gc.IsNil)
58 c.Assert(server, gc.NotNil)
59 s.instWithoutIP = &sigmaInstance{server}
60}
61
62func (s *instanceSuite) TearDownTest(c *gc.C) {
63 mock.ResetServers()
64 s.BaseSuite.TearDownTest(c)
65}
66
67func (s *instanceSuite) TestInstanceEmpty(c *gc.C) {
68 e := sigmaInstance{}
69 c.Check(e.Id(), gc.Equals, instance.Id(""))
70 c.Check(e.Status(), gc.Equals, "")
71 c.Check(e.Refresh(), gc.ErrorMatches, "invalid instance")
72
73 addrs, err := e.Addresses()
74 c.Check(addrs, gc.IsNil)
75 c.Check(err, gc.Equals, instance.ErrNoDNSName)
76
77 name, err := e.DNSName()
78 c.Check(name, gc.Equals, "")
79 c.Check(err, gc.ErrorMatches, "invalid instance")
80
81 wname, err := e.WaitDNSName()
82 c.Check(wname, gc.Equals, "")
83 c.Check(err, gc.ErrorMatches, "invalid instance")
84
85 c.Check(e.hardware(), gc.IsNil)
86}
87
88func (s *instanceSuite) TestInstanceId(c *gc.C) {
89 c.Check(s.inst.Id(), gc.Equals, instance.Id("f4ec5097-121e-44a7-a207-75bc02163260"))
90}
91
92func (s *instanceSuite) TestInstanceStatus(c *gc.C) {
93 c.Check(s.inst.Status(), gc.Equals, "running")
94}
95
96func (s *instanceSuite) TestInstanceRefresh(c *gc.C) {
97 c.Check(s.inst.Status(), gc.Equals, "running")
98
99 mock.SetServerStatus("f4ec5097-121e-44a7-a207-75bc02163260", "stopped")
100
101 err := s.inst.Refresh()
102 c.Check(err, gc.IsNil)
103
104 c.Check(s.inst.Status(), gc.Equals, "stopped")
105}
106
107func (s *instanceSuite) TestInstanceAddresses(c *gc.C) {
108 addrs, err := s.inst.Addresses()
109 c.Check(addrs, gc.HasLen, 1)
110 c.Check(err, gc.IsNil)
111 a := addrs[0]
112 c.Check(a.Value, gc.Equals, "178.22.70.33")
113 c.Check(a.Type, gc.Equals, instance.Ipv4Address)
114 c.Check(a.NetworkScope, gc.Equals, instance.NetworkPublic)
115 c.Check(a.NetworkName, gc.Equals, "")
116
117 addrs, err = s.instWithoutIP.Addresses()
118 c.Check(addrs, gc.IsNil)
119 c.Check(err, gc.Equals, instance.ErrNoDNSName)
120}
121
122func (s *instanceSuite) TestInstanceDNSName(c *gc.C) {
123 name, err := s.inst.DNSName()
124 c.Check(name, gc.Equals, "178.22.70.33")
125 c.Check(err, gc.IsNil)
126
127 name, err = s.instWithoutIP.DNSName()
128 c.Check(name, gc.Equals, "")
129 c.Check(err, gc.Equals, instance.ErrNoDNSName)
130
131 wname, err := s.inst.WaitDNSName()
132 c.Check(wname, gc.Equals, "178.22.70.33")
133 c.Check(err, gc.IsNil)
134
135 go func() {
136 mock.AddServer(&data.Server{
137 Resource: data.Resource{URI: "uri", UUID: "uuid-no-ip"},
138 NICs: []data.NIC{
139 data.NIC{
140 IPv4: &data.IPv4{
141 Conf: "static",
142 IP: data.MakeIPResource("31.171.246.37"),
143 },
144 Runtime: &data.RuntimeNetwork{
145 InterfaceType: "public",
146 IPv4: data.MakeIPResource("31.171.246.37"),
147 },
148 },
149 },
150 })
151 }()
152
153 wname, err = s.instWithoutIP.WaitDNSName()
154 c.Check(wname, gc.Equals, "31.171.246.37")
155 c.Check(err, gc.IsNil)
156}
157
158func (s *instanceSuite) TestInstancePorts(c *gc.C) {
159 c.Check(s.inst.OpenPorts("", nil), gc.IsNil)
160 c.Check(s.inst.ClosePorts("", nil), gc.IsNil)
161
162 ports, err := s.inst.Ports("")
163 c.Check(ports, gc.NotNil)
164 if ports != nil {
165 c.Check(ports, gc.HasLen, 0)
166 }
167 c.Check(err, gc.IsNil)
168}
169
170func (s *instanceSuite) TestInstanceHardware(c *gc.C) {
171 hw := s.inst.hardware()
172 c.Assert(hw, gc.NotNil)
173
174 c.Check(hw.Arch, gc.IsNil)
175
176 c.Check(hw.Mem, gc.NotNil)
177 if hw.Mem != nil {
178 c.Check(*hw.Mem, gc.Equals, uint64(2048))
179 }
180
181 c.Check(hw.RootDisk, gc.IsNil)
182
183 c.Check(hw.CpuCores, gc.NotNil)
184 if hw.CpuCores != nil {
185 c.Check(*hw.CpuCores, gc.Equals, uint64(1))
186 }
187
188 c.Check(hw.CpuPower, gc.NotNil)
189 if hw.CpuPower != nil {
190 c.Check(*hw.CpuPower, gc.Equals, uint64(2000))
191 }
192
193 c.Check(hw.Tags, gc.IsNil)
194}
195
196const jsonInstanceData = `{
197 "context": true,
198 "cpu": 2000,
199 "cpu_model": null,
200 "cpus_instead_of_cores": false,
201 "drives": [
202 {
203 "boot_order": 1,
204 "dev_channel": "0:0",
205 "device": "virtio",
206 "drive": {
207 "resource_uri": "/api/2.0/drives/f968dc48-25a0-4d46-8f16-e12e073e1fe6/",
208 "uuid": "f968dc48-25a0-4d46-8f16-e12e073e1fe6"
209 },
210 "runtime": {
211 "io": {
212 "bytes_read": 82980352,
213 "bytes_written": 189440,
214 "count_flush": 0,
215 "count_read": 3952,
216 "count_written": 19,
217 "total_time_ns_flush": 0,
218 "total_time_ns_read": 4435322816,
219 "total_time_ns_write": 123240430
220 }
221 }
222 }
223 ],
224 "enable_numa": false,
225 "hv_relaxed": false,
226 "hv_tsc": false,
227 "jobs": [],
228 "mem": 2147483648,
229 "meta": {
230 "description": "test-description",
231 "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDiwTGBsmFKBYHcKaVy5IgsYBR4XVYLS6KP/NKClE7gONlIGURE3+/45BX8TfHJHM5WTN8NBqJejKDHqwfyueR1f2VGoPkJxODGt/X/ZDNftLZLYwPd2DfDBs27ahOadZCk4Cl5l7mU0aoE74UnIcQoNPl6w7axkIFTIXr8+0HMk8DFB0iviBSJK118p1RGwhsoA1Hudn1CsgqARGPmNn6mxwvmQfQY7hZxZoOH9WMcvkNZ7rAFrwS/BuvEpEXkoC95K/JDPvmQVVJk7we+WeHfTYSmApkDFcSaypyjL2HOV8pvE+VntcIIhZccHiOubyjsBAx5aoTI+ueCsoz5AL1 maxim.perenesenko@altoros.com"
232 },
233 "name": "LiveTest-srv-17-54-51-999999999",
234 "nics": [
235 {
236 "boot_order": null,
237 "firewall_policy": null,
238 "ip_v4_conf": {
239 "conf": "dhcp",
240 "ip": null
241 },
242 "ip_v6_conf": null,
243 "mac": "22:ab:bf:26:e1:be",
244 "model": "virtio",
245 "runtime": {
246 "interface_type": "public",
247 "io": {
248 "bytes_recv": 0,
249 "bytes_sent": 17540,
250 "packets_recv": 0,
251 "packets_sent": 256
252 },
253 "ip_v4": {
254 "resource_uri": "/api/2.0/ips/178.22.70.33/",
255 "uuid": "178.22.70.33"
256 },
257 "ip_v6": null
258 },
259 "vlan": null
260 },
261 {
262 "boot_order": null,
263 "firewall_policy": null,
264 "ip_v4_conf": null,
265 "ip_v6_conf": null,
266 "mac": "22:9e:e7:d7:86:94",
267 "model": "virtio",
268 "runtime": {
269 "interface_type": "private",
270 "io": {
271 "bytes_recv": 0,
272 "bytes_sent": 1046,
273 "packets_recv": 0,
274 "packets_sent": 13
275 },
276 "ip_v4": null,
277 "ip_v6": null
278 },
279 "vlan": {
280 "resource_uri": "/api/2.0/vlans/5bc05e7e-6555-4f40-add8-3b8e91447702/",
281 "uuid": "5bc05e7e-6555-4f40-add8-3b8e91447702"
282 }
283 }
284 ],
285 "owner": {
286 "resource_uri": "/api/2.0/user/c25eb0ed-161f-44f4-ac1d-d584ce3a5312/",
287 "uuid": "c25eb0ed-161f-44f4-ac1d-d584ce3a5312"
288 },
289 "requirements": [],
290 "resource_uri": "/api/2.0/servers/f4ec5097-121e-44a7-a207-75bc02163260/",
291 "runtime": {
292 "active_since": "2014-04-24T14:56:58+00:00",
293 "nics": [
294 {
295 "interface_type": "public",
296 "io": {
297 "bytes_recv": 0,
298 "bytes_sent": 17540,
299 "packets_recv": 0,
300 "packets_sent": 256
301 },
302 "ip_v4": {
303 "resource_uri": "/api/2.0/ips/178.22.70.33/",
304 "uuid": "178.22.70.33"
305 },
306 "ip_v6": null,
307 "mac": "22:ab:bf:26:e1:be"
308 },
309 {
310 "interface_type": "private",
311 "io": {
312 "bytes_recv": 0,
313 "bytes_sent": 1046,
314 "packets_recv": 0,
315 "packets_sent": 13
316 },
317 "ip_v4": null,
318 "ip_v6": null,
319 "mac": "22:9e:e7:d7:86:94"
320 }
321 ]
322 },
323 "smp": 1,
324 "status": "running",
325 "tags": [],
326 "uuid": "f4ec5097-121e-44a7-a207-75bc02163260",
327 "vnc_password": "test-vnc-password"
328}`
0329
=== added file 'provider/cloudsigma/provider.go'
--- provider/cloudsigma/provider.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/provider.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,140 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4// Juju provider for CloudSigma
5
6package cloudsigma
7
8import (
9 "fmt"
10
11 "github.com/juju/loggo"
12
13 "launchpad.net/juju-core/environs"
14 "launchpad.net/juju-core/environs/config"
15)
16
17var logger = loggo.GetLogger("juju.provider.cloudsigma")
18
19type loggerWriter struct {
20 level loggo.Level
21}
22
23func (lw loggerWriter) Write(p []byte) (n int, err error) {
24 logger.Logf(lw.level, string(p))
25 return len(p), nil
26}
27
28type environProvider struct{}
29
30var providerInstance = environProvider{}
31
32// check the provider implements environs.EnvironProvider interface
33var _ environs.EnvironProvider = (*environProvider)(nil)
34
35func init() {
36 // This will only happen in binaries that actually import this provider
37 // somewhere. To enable a provider, import it in the "providers/all"
38 // package; please do *not* import individual providers anywhere else,
39 // except in direct tests for that provider.
40 environs.RegisterProvider("cloudsigma", providerInstance)
41}
42
43// Boilerplate returns a default configuration for the environment in yaml format.
44// The text should be a key followed by some number of attributes:
45// `environName:
46// type: environTypeName
47// attr1: val1
48// `
49// The text is used as a template (see the template package) with one extra template
50// function available, rand, which expands to a random hexadecimal string when invoked.
51func (environProvider) BoilerplateConfig() string {
52 return boilerplateConfig
53}
54
55// Open opens the environment and returns it.
56// The configuration must have come from a previously
57// prepared environment.
58func (environProvider) Open(cfg *config.Config) (environs.Environ, error) {
59 logger.Infof("opening environment %q", cfg.Name())
60
61 cfg, err := prepareConfig(cfg)
62 if err != nil {
63 return nil, err
64 }
65
66 env := &environ{name: cfg.Name()}
67 if err := env.SetConfig(cfg); err != nil {
68 return nil, err
69 }
70
71 return env, nil
72}
73
74// Prepare prepares an environment for use. Any additional
75// configuration attributes in the returned environment should
76// be saved to be used later. If the environment is already
77// prepared, this call is equivalent to Open.
78func (environProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
79 logger.Infof("preparing environment %q", cfg.Name())
80 return providerInstance.Open(cfg)
81}
82
83// Validate ensures that config is a valid configuration for this
84// provider, applying changes to it if necessary, and returns the
85// validated configuration.
86// If old is not nil, it holds the previous environment configuration
87// for consideration when validating changes.
88func (environProvider) Validate(cfg, old *config.Config) (*config.Config, error) {
89 logger.Infof("validating environment %q", cfg.Name())
90
91 // You should almost certainly not change this method; if you need to change
92 // how configs are validated, you should edit validateConfig itself, to ensure
93 // that your checks are always applied.
94 newEcfg, err := validateConfig(cfg, nil)
95 if err != nil {
96 return nil, fmt.Errorf("invalid config: %v", err)
97 }
98 if old != nil {
99 oldEcfg, err := validateConfig(old, nil)
100 if err != nil {
101 return nil, fmt.Errorf("invalid base config: %v", err)
102 }
103 if newEcfg, err = validateConfig(cfg, oldEcfg); err != nil {
104 return nil, fmt.Errorf("invalid config change: %v", err)
105 }
106 }
107
108 return newEcfg.Config, nil
109}
110
111// SecretAttrs filters the supplied configuration returning only values
112// which are considered sensitive. All of the values of these secret
113// attributes need to be strings.
114func (environProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
115 logger.Infof("filtering secret attributes for environment %q", cfg.Name())
116
117 // If you keep configSecretFields up to date, this method should Just Work.
118 ecfg, err := validateConfig(cfg, nil)
119 if err != nil {
120 return nil, err
121 }
122 secretAttrs := map[string]string{}
123 for _, field := range configSecretFields {
124 if value, ok := ecfg.attrs[field]; ok {
125 if stringValue, ok := value.(string); ok {
126 secretAttrs[field] = stringValue
127 } else {
128 // All your secret attributes must be strings at the moment. Sorry.
129 // It's an expedient and hopefully temporary measure that helps us
130 // plug a security hole in the API.
131 return nil, fmt.Errorf(
132 "secret %q field must have a string value; got %v",
133 field, value,
134 )
135 }
136 }
137 }
138
139 return secretAttrs, nil
140}
0141
=== added file 'provider/cloudsigma/provider_test.go'
--- provider/cloudsigma/provider_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/provider_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,94 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "flag"
8 "io"
9 "testing"
10
11 gc "launchpad.net/gocheck"
12 tt "launchpad.net/juju-core/testing"
13 "launchpad.net/juju-core/utils"
14)
15
16var live = flag.Bool("live", false, "run tests on live CloudSigma account")
17
18func TestCloudSigma(t *testing.T) {
19 /*
20 if *live {
21 registerLiveTests()
22 }
23 */
24 gc.TestingT(t)
25}
26
27type providerSuite struct {
28 tt.BaseSuite
29}
30
31var _ = gc.Suite(&providerSuite{})
32
33func (s *providerSuite) TestProviderBoilerplateConfig(c *gc.C) {
34 cfg := providerInstance.BoilerplateConfig()
35 c.Assert(cfg, gc.Not(gc.Equals), "")
36}
37
38type failReader struct {
39 err error
40}
41
42func (f *failReader) Read(p []byte) (n int, err error) {
43 return 0, f.err
44}
45
46type fakeStorage struct {
47 call string
48 name string
49 prefix string
50 err error
51 reader io.Reader
52 length int64
53}
54
55func (f *fakeStorage) Get(name string) (io.ReadCloser, error) {
56 f.call = "Get"
57 f.name = name
58 return nil, nil
59}
60func (f *fakeStorage) List(prefix string) ([]string, error) {
61 f.call = "List"
62 f.prefix = prefix
63 return []string{prefix}, nil
64}
65func (f *fakeStorage) URL(name string) (string, error) {
66 f.call = "URL"
67 f.name = name
68 return "", nil
69}
70func (f *fakeStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
71 f.call = "DefaultConsistencyStrategy"
72 return utils.AttemptStrategy{}
73}
74func (f *fakeStorage) ShouldRetry(err error) bool {
75 f.call = "ShouldRetry"
76 f.err = err
77 return false
78}
79func (f *fakeStorage) Put(name string, r io.Reader, length int64) error {
80 f.call = "Put"
81 f.name = name
82 f.reader = r
83 f.length = length
84 return nil
85}
86func (f *fakeStorage) Remove(name string) error {
87 f.call = "Remove"
88 f.name = name
89 return nil
90}
91func (f *fakeStorage) RemoveAll() error {
92 f.call = "RemoveAll"
93 return nil
94}
095
=== added file 'provider/cloudsigma/storage.go'
--- provider/cloudsigma/storage.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/storage.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,252 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "bytes"
8 "fmt"
9 "github.com/juju/loggo"
10 "io"
11 "io/ioutil"
12 "os"
13 "path"
14 "strings"
15
16 "launchpad.net/juju-core/agent"
17 "launchpad.net/juju-core/environs/filestorage"
18 "launchpad.net/juju-core/environs/httpstorage"
19 "launchpad.net/juju-core/environs/sshstorage"
20 "launchpad.net/juju-core/environs/storage"
21 "launchpad.net/juju-core/juju/osenv"
22 "launchpad.net/juju-core/utils"
23)
24
25const (
26 // storageSubdir is the subdirectory of
27 // dataDir in which storage will be located.
28 storageSubdir = "storage"
29
30 // storageTmpSubdir is the subdirectory of
31 // dataDir in which temporary storage will
32 // be located.
33 storageTmpSubdir = "storage-tmp"
34)
35
36type environStorage struct {
37 storage storage.Storage
38 uuid string
39 tmp bool
40}
41
42var _ storage.Storage = (*environStorage)(nil)
43
44var newStorage = func(ecfg *environConfig, client *environClient) (*environStorage, error) {
45 var stg storage.Storage
46 var err error
47 var tmp bool
48
49 uuid, ip, ok := client.stateServerAddress()
50 if ok {
51 logger.Debugf("active state server: %q", ip)
52 // create https storage client
53 stg, err = newRemoteStorage(ecfg, ip)
54 } else {
55 // create tmp storage
56 tmp = true
57 path := path.Join(osenv.JujuHome(), "tmp")
58
59 logger.Debugf("prepare %q for tmp storage", path)
60 logger.Debugf(" removing all...")
61 if err := os.RemoveAll(path); err != nil {
62 return nil, fmt.Errorf("create tmp storage (RemoveAll): %v", err)
63 }
64
65 logger.Debugf(" creating path...")
66 if err := os.MkdirAll(path, 0755); err != nil {
67 return nil, fmt.Errorf("create tmp storage (MkdirAll): %v", err)
68 }
69 stg, err = filestorage.NewFileStorageWriter(path)
70 logger.Debugf("using local tmp storage at: %q", path)
71 }
72
73 if err != nil {
74 return nil, err
75 }
76
77 result := &environStorage{
78 storage: stg,
79 uuid: uuid,
80 tmp: tmp,
81 }
82
83 client.storage = result
84
85 return result, nil
86}
87
88func newRemoteStorage(ecfg *environConfig, ip string) (storage.Storage, error) {
89 caCertPEM, ok := ecfg.CACert()
90 if !ok {
91 // should not be possible to validate base config
92 return nil, fmt.Errorf("ca-cert not set")
93 }
94
95 addr := fmt.Sprintf("%s:%d", ip, ecfg.storagePort())
96 logger.Debugf("using https storage at: %q", addr)
97
98 authkey := ecfg.storageAuthKey()
99 stg, err := httpstorage.ClientTLS(addr, caCertPEM, authkey)
100 if err != nil {
101 return nil, fmt.Errorf("initializing HTTPS storage failed: %v", err)
102 }
103
104 return stg, nil
105}
106
107func (s *environStorage) onStateInstanceStop(uuid string) {
108 if s.uuid != uuid {
109 return
110 }
111 s.storage = nil
112 s.uuid = ""
113 s.tmp = false
114}
115
116func (s *environStorage) List(prefix string) ([]string, error) {
117 if s.storage == nil {
118 return nil, fmt.Errorf("storage is not initialized")
119 }
120 list, err := s.storage.List(prefix)
121 logger.Tracef("environStorage.List, prefix = %s, len = %d, err = %v", prefix, len(list), err)
122 if err == nil && logger.LogLevel() <= loggo.TRACE {
123 for _, name := range list {
124 logger.Tracef("...%q", name)
125 }
126 }
127 return list, err
128}
129
130func (s *environStorage) URL(name string) (string, error) {
131 if s.storage == nil {
132 return "", fmt.Errorf("storage is not initialized")
133 }
134 if s.tmp {
135 return "%s/" + name, nil
136 }
137 url, err := s.storage.URL(name)
138 logger.Tracef("environStorage.URL, name = %q, url = %q, err = %v", name, url, err)
139 return url, err
140}
141
142func (s *environStorage) Get(name string) (io.ReadCloser, error) {
143 if s.storage == nil {
144 return nil, fmt.Errorf("storage is not initialized")
145 }
146 r, err := s.storage.Get(name)
147 logger.Tracef("environStorage.Get, name = %q, err = %v", name, err)
148 return r, err
149}
150
151func (s *environStorage) Put(name string, r io.Reader, length int64) error {
152 if s.storage == nil {
153 return fmt.Errorf("storage is not initialized")
154 }
155 err := s.storage.Put(name, r, length)
156 logger.Tracef("environStorage.Put, name = %q, len = %d, err = %v", name, length, err)
157 return err
158}
159
160func (s *environStorage) Remove(name string) error {
161 if s.storage == nil {
162 return fmt.Errorf("storage is not initialized")
163 }
164 err := s.storage.Remove(name)
165 logger.Tracef("environStorage.Remove, name = %q, err = %v", name, err)
166 return err
167}
168
169func (s *environStorage) RemoveAll() error {
170 if s.storage == nil {
171 // this method is called after state server destroy at destroy-environment
172 return nil
173 }
174 err := s.storage.RemoveAll()
175 logger.Tracef("environStorage.RemoveAll, err = %v", err)
176 return err
177}
178
179func (s *environStorage) DefaultConsistencyStrategy() utils.AttemptStrategy {
180 if s.storage == nil {
181 return utils.AttemptStrategy{}
182 }
183 return s.storage.DefaultConsistencyStrategy()
184}
185
186func (s *environStorage) ShouldRetry(err error) bool {
187 if s.storage == nil {
188 return false
189 }
190 return s.storage.ShouldRetry(err)
191}
192
193var newSSHStorage = func(sshurl, dir, tmpdir string) (storage.Storage, error) {
194 return sshstorage.NewSSHStorage(sshstorage.NewSSHStorageParams{
195 Host: sshurl,
196 StorageDir: dir,
197 TmpDir: tmpdir,
198 })
199}
200
201func (s *environStorage) MoveToSSH(user, host string) error {
202 if s.storage == nil {
203 return fmt.Errorf("storage is not initialized")
204 }
205
206 sshurl := user + "@" + host
207 if !s.tmp {
208 return fmt.Errorf("failed to move non-temporary storage to %q", sshurl)
209 }
210
211 storageDir := path.Join(agent.DefaultDataDir, storageSubdir)
212 storageTmpdir := path.Join(agent.DefaultDataDir, storageTmpSubdir)
213
214 logger.Debugf("using ssh storage at host %q dir %q", sshurl, storageDir)
215 stor, err := newSSHStorage(sshurl, storageDir, storageTmpdir)
216 if err != nil {
217 return fmt.Errorf("initializing SSH storage failed: %v", err)
218 }
219
220 list, err := s.List("")
221 if err != nil {
222 return fmt.Errorf("listing tmp storage failed: %v", err)
223 }
224
225 logger.Tracef("list to move:\n%s", strings.Join(list, "\n"))
226
227 for _, path := range list {
228 r, err := s.Get(path)
229 if err != nil {
230 return fmt.Errorf("getting %q from tmp storage failed: %v", path, err)
231 }
232 defer r.Close()
233
234 bb, err := ioutil.ReadAll(r)
235 if err != nil {
236 return fmt.Errorf("error MoveToSSH: reading %q from ssh storage failed: %v", path, err)
237 }
238
239 rb := bytes.NewReader(bb)
240 length := len(bb)
241 if err := stor.Put(path, rb, int64(length)); err != nil {
242 return fmt.Errorf("error MoveToSSH: putting %q to ssh storage failed: %v", path, err)
243 }
244 }
245
246 s.storage.RemoveAll()
247
248 s.storage = stor
249 s.tmp = false
250
251 return nil
252}
0253
=== added file 'provider/cloudsigma/storage_test.go'
--- provider/cloudsigma/storage_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/storage_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,373 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8 "github.com/juju/loggo"
9 "io"
10 "io/ioutil"
11 "strings"
12
13 gc "launchpad.net/gocheck"
14 "launchpad.net/juju-core/environs/config"
15 "launchpad.net/juju-core/environs/storage"
16 "launchpad.net/juju-core/environs/testing"
17 tt "launchpad.net/juju-core/testing"
18 "launchpad.net/juju-core/utils"
19)
20
21type StorageSuite struct {
22 tt.BaseSuite
23}
24
25var _ = gc.Suite(&StorageSuite{})
26
27func (s *StorageSuite) TestStorageEmpty(c *gc.C) {
28 stg := &environStorage{}
29
30 lst, err := stg.List("")
31 c.Check(lst, gc.IsNil)
32 c.Check(err, gc.NotNil)
33
34 url, err := stg.URL("")
35 c.Check(url, gc.Equals, "")
36 c.Check(err, gc.NotNil)
37
38 r, err := stg.Get("")
39 c.Check(r, gc.IsNil)
40 c.Check(err, gc.NotNil)
41
42 err = stg.Put("", nil, 0)
43 c.Check(err, gc.NotNil)
44
45 err = stg.Remove("")
46 c.Check(err, gc.NotNil)
47
48 err = stg.RemoveAll()
49 c.Check(err, gc.IsNil)
50
51 strategy := stg.DefaultConsistencyStrategy()
52 c.Check(strategy, gc.Equals, utils.AttemptStrategy{})
53
54 shr := stg.ShouldRetry(nil)
55 c.Check(shr, gc.Equals, false)
56
57 err = stg.MoveToSSH("", "")
58 c.Check(err, gc.NotNil)
59}
60
61func (s *StorageSuite) TestStorageInstanceStop(c *gc.C) {
62 stg := &environStorage{
63 storage: &fakeStorage{},
64 uuid: "uuid",
65 tmp: true,
66 }
67 stg.onStateInstanceStop("")
68 c.Check(stg.uuid, gc.Equals, "uuid")
69 stg.onStateInstanceStop("uuid")
70 c.Check(stg.storage, gc.IsNil)
71 c.Check(stg.uuid, gc.Equals, "")
72 c.Check(stg.tmp, gc.Equals, false)
73}
74
75func (s *StorageSuite) TestStorageURL(c *gc.C) {
76 fs := &fakeStorage{}
77 stg := &environStorage{
78 storage: fs,
79 uuid: "uuid",
80 tmp: true,
81 }
82
83 url, err := stg.URL("")
84 c.Check(err, gc.IsNil)
85 c.Check(url, gc.Equals, "%s/")
86
87 url, err = stg.URL("path/name")
88 c.Check(err, gc.IsNil)
89 c.Check(url, gc.Equals, "%s/path/name")
90
91 stg.tmp = false
92
93 url, err = stg.URL("path/name")
94 c.Check(err, gc.IsNil)
95 c.Check(url, gc.Equals, "")
96 c.Check(fs.call, gc.Equals, "URL")
97 c.Check(fs.name, gc.Equals, "path/name")
98}
99
100func (s *StorageSuite) TestStorageProxy(c *gc.C) {
101 fs := &fakeStorage{}
102 stg := &environStorage{
103 storage: fs,
104 uuid: "uuid",
105 tmp: true,
106 }
107
108 test := func(s storage.Storage) {
109
110 ll := logger.LogLevel()
111 logger.SetLogLevel(loggo.TRACE)
112 s.List("list")
113 c.Check(fs.call, gc.Equals, "List")
114 c.Check(fs.prefix, gc.Equals, "list")
115 logger.SetLogLevel(ll)
116
117 s.Get("get")
118 c.Check(fs.call, gc.Equals, "Get")
119 c.Check(fs.name, gc.Equals, "get")
120
121 r := strings.NewReader("")
122 s.Put("put", r, 1024)
123 c.Check(fs.call, gc.Equals, "Put")
124 c.Check(fs.name, gc.Equals, "put")
125 c.Check(fs.reader, gc.Equals, r)
126 c.Check(fs.length, gc.Equals, int64(1024))
127
128 s.Remove("remove")
129 c.Check(fs.call, gc.Equals, "Remove")
130 c.Check(fs.name, gc.Equals, "remove")
131
132 s.RemoveAll()
133 c.Check(fs.call, gc.Equals, "RemoveAll")
134
135 s.DefaultConsistencyStrategy()
136 c.Check(fs.call, gc.Equals, "DefaultConsistencyStrategy")
137
138 err := fmt.Errorf("test")
139 s.ShouldRetry(err)
140 c.Check(fs.call, gc.Equals, "ShouldRetry")
141 c.Check(fs.err, gc.Equals, err)
142 }
143
144 test(stg)
145
146 stg.tmp = false
147 test(stg)
148}
149
150func (s *StorageSuite) TestStorageMoveNonTemp(c *gc.C) {
151 fs := &fakeStorage{}
152 stg := &environStorage{
153 storage: fs,
154 uuid: "uuid",
155 tmp: false,
156 }
157
158 err := stg.MoveToSSH("", "")
159 c.Check(err, gc.NotNil)
160}
161
162func (s *StorageSuite) TestStorageMoveFailSsh(c *gc.C) {
163 fs := &fakeStorage{}
164 stg := &environStorage{
165 storage: fs,
166 uuid: "uuid",
167 tmp: true,
168 }
169
170 s.PatchValue(&newSSHStorage,
171 func(sshurl, dir, tmpdir string) (storage.Storage, error) {
172 return nil, fmt.Errorf("test")
173 })
174
175 err := stg.MoveToSSH("", "")
176 c.Check(err, gc.NotNil)
177 c.Check(err, gc.ErrorMatches, "initializing SSH storage failed: test")
178}
179
180func (s *StorageSuite) TestStorageMoveFailList(c *gc.C) {
181 fs := &fakeStorage{}
182 stg := &environStorage{
183 storage: fs,
184 uuid: "uuid",
185 tmp: true,
186 }
187
188 s.PatchValue(&newSSHStorage,
189 func(sshurl, dir, tmpdir string) (storage.Storage, error) {
190 stg.storage = nil
191 return nil, nil
192 })
193
194 err := stg.MoveToSSH("", "")
195 c.Check(err, gc.NotNil)
196 c.Check(err, gc.ErrorMatches, "listing tmp storage failed: storage is not initialized")
197}
198
199func newTestStorage(s *StorageSuite, c *gc.C) storage.Storage {
200 closer, stor, _ := testing.CreateLocalTestStorage(c)
201 s.AddCleanup(func(*gc.C) { closer.Close() })
202 return stor
203}
204
205type moveStorageTest struct {
206 stg storage.Storage
207 handler func(_, _, _ string) (storage.Storage, error)
208 data map[string]string
209 emsg string
210}
211
212func (s *StorageSuite) testMoveStorage(c *gc.C, d *moveStorageTest) {
213
214 s.PatchValue(&newSSHStorage, d.handler)
215
216 for k, v := range d.data {
217 err := d.stg.Put(k, strings.NewReader(v), int64(len(v)))
218 c.Assert(err, gc.IsNil)
219 }
220
221 src := &environStorage{
222 storage: d.stg,
223 uuid: "uuid",
224 tmp: true,
225 }
226
227 err := src.MoveToSSH("user", "host")
228 if d.emsg == "" {
229 c.Check(err, gc.IsNil)
230 } else {
231 c.Check(err, gc.ErrorMatches, d.emsg)
232 return
233 }
234
235 kk, err := src.storage.List("")
236 c.Assert(err, gc.IsNil)
237
238 var result = make(map[string]string, len(kk))
239 for _, k := range kk {
240 r, err := src.storage.Get(k)
241 c.Assert(err, gc.IsNil)
242 defer r.Close()
243 bb, err := ioutil.ReadAll(r)
244 c.Assert(err, gc.IsNil)
245 result[k] = string(bb)
246 }
247
248 for k, v := range d.data {
249 if vv, ok := result[k]; !ok {
250 c.Errorf("key %s not found", k)
251 } else {
252 c.Check(vv, gc.Equals, v)
253 }
254 }
255}
256
257func (s *StorageSuite) TestStorageMoveEmpty(c *gc.C) {
258 data := &moveStorageTest{
259 stg: newTestStorage(s, c),
260 handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) {
261 c.Check(sshurl, gc.Equals, "user@host")
262 return newTestStorage(s, c), nil
263 }}
264 s.testMoveStorage(c, data)
265}
266
267var moveData = map[string]string{
268 "test0": "0987654321",
269 "test/test1": "1234567890",
270}
271
272func (s *StorageSuite) TestStorageMoveData(c *gc.C) {
273 data := &moveStorageTest{
274 stg: newTestStorage(s, c),
275 handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) {
276 c.Check(sshurl, gc.Equals, "user@host")
277 return newTestStorage(s, c), nil
278 },
279 data: moveData,
280 }
281 s.testMoveStorage(c, data)
282}
283
284type storageProxyGetFailed struct {
285 storage.Storage
286}
287
288func (s *storageProxyGetFailed) Get(name string) (io.ReadCloser, error) {
289 return nil, fmt.Errorf("test")
290}
291
292func (s *StorageSuite) TestStorageMoveGetFailed(c *gc.C) {
293 data := &moveStorageTest{
294 stg: &storageProxyGetFailed{newTestStorage(s, c)},
295 handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) {
296 c.Check(sshurl, gc.Equals, "user@host")
297 return newTestStorage(s, c), nil
298 },
299 data: moveData,
300 emsg: "getting .* from tmp storage failed: test",
301 }
302 s.testMoveStorage(c, data)
303}
304
305type storageProxyGetReadFailed struct {
306 storage.Storage
307}
308
309func (s *storageProxyGetReadFailed) Get(name string) (io.ReadCloser, error) {
310 err := fmt.Errorf("test")
311 r := &failReader{err}
312 return ioutil.NopCloser(r), nil
313}
314
315func (s *StorageSuite) TestStorageMoveGetReadFailed(c *gc.C) {
316 data := &moveStorageTest{
317 stg: &storageProxyGetReadFailed{newTestStorage(s, c)},
318 handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) {
319 c.Check(sshurl, gc.Equals, "user@host")
320 return newTestStorage(s, c), nil
321 },
322 data: moveData,
323 emsg: ".*MoveToSSH: reading .* from ssh storage failed: test",
324 }
325 s.testMoveStorage(c, data)
326}
327
328type storageProxyPutFailed struct {
329 storage.Storage
330}
331
332func (s *storageProxyPutFailed) Put(name string, r io.Reader, length int64) error {
333 return fmt.Errorf("test")
334}
335
336func (s *StorageSuite) TestStorageMovePutFailed(c *gc.C) {
337 data := &moveStorageTest{
338 stg: newTestStorage(s, c),
339 handler: func(sshurl, dir, tmpdir string) (storage.Storage, error) {
340 c.Check(sshurl, gc.Equals, "user@host")
341 return &storageProxyPutFailed{newTestStorage(s, c)}, nil
342 },
343 data: moveData,
344 emsg: ".*MoveToSSH: putting .* to ssh storage failed: test",
345 }
346 s.testMoveStorage(c, data)
347}
348
349func (s *StorageSuite) TestStorageNewRemoteStorage(c *gc.C) {
350 ecfg := &environConfig{
351 Config: newConfig(c, tt.Attrs{
352 "name": "client-test",
353 }),
354 attrs: map[string]interface{}{
355 "storage-port": 1234,
356 },
357 }
358 stg, err := newRemoteStorage(ecfg, "0.1.2.3")
359 c.Check(stg, gc.NotNil)
360 c.Check(err, gc.IsNil)
361
362 attrs := tt.FakeConfig().Delete("ca-cert", "ca-private-key")
363 cfg, err := config.New(config.NoDefaults, attrs)
364 c.Assert(err, gc.IsNil)
365 ecfg = &environConfig{
366 Config: cfg,
367 attrs: map[string]interface{}{
368 "storage-port": 1234,
369 }}
370 stg, err = newRemoteStorage(ecfg, "0.1.2.3")
371 c.Check(stg, gc.IsNil)
372 c.Check(err, gc.ErrorMatches, "ca-cert not set")
373}
0374
=== added file 'provider/cloudsigma/storageconfig.go'
--- provider/cloudsigma/storageconfig.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/storageconfig.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,59 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 "fmt"
8
9 "launchpad.net/juju-core/worker/localstorage"
10)
11
12// storageConfig is an struct implementing LocalTLSStorageConfig interface
13// to support serving storage over TLS.
14type storageConfig struct {
15 ecfg *environConfig
16 storageDir string
17 storageAddr string
18 storagePort int
19}
20
21var _ localstorage.LocalTLSStorageConfig = (*storageConfig)(nil)
22
23// StorageDir is a storage local directory
24func (c storageConfig) StorageDir() string {
25 return c.storageDir
26}
27
28// StorageAddr is a storage IP address and port
29func (c storageConfig) StorageAddr() string {
30 return fmt.Sprintf("%s:%d", c.storageAddr, c.storagePort)
31}
32
33// StorageCACert is the CA certificate in PEM format.
34func (c storageConfig) StorageCACert() string {
35 if cert, ok := c.ecfg.CACert(); ok {
36 return cert
37 }
38 return ""
39}
40
41// StorageCAKey is the CA private key in PEM format.
42func (c storageConfig) StorageCAKey() string {
43 if key, ok := c.ecfg.CAPrivateKey(); ok {
44 return key
45 }
46 return ""
47}
48
49// StorageHostnames is the set of hostnames that will
50// be assigned to the storage server's certificate.
51func (c storageConfig) StorageHostnames() []string {
52 return []string{c.storageAddr}
53}
54
55// StorageAuthKey is the key that clients must present
56// to perform modifying operations.
57func (c storageConfig) StorageAuthKey() string {
58 return c.ecfg.storageAuthKey()
59}
060
=== added file 'provider/cloudsigma/storageconfig_test.go'
--- provider/cloudsigma/storageconfig_test.go 1970-01-01 00:00:00 +0000
+++ provider/cloudsigma/storageconfig_test.go 2014-06-09 11:10:33 +0000
@@ -0,0 +1,58 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package cloudsigma
5
6import (
7 gc "launchpad.net/gocheck"
8 "launchpad.net/juju-core/environs/config"
9 "launchpad.net/juju-core/testing"
10)
11
12func newStorageConfig(c *gc.C, attrs testing.Attrs) *storageConfig {
13 attrs = testing.FakeConfig().Merge(attrs)
14 cfg, err := config.New(config.NoDefaults, attrs)
15 c.Assert(err, gc.IsNil)
16 ecfg, err := validateConfig(cfg, nil)
17 c.Assert(err, gc.IsNil)
18 return &storageConfig{ecfg: ecfg}
19}
20
21type StorageConfigSuite struct {
22 testing.BaseSuite
23}
24
25var _ = gc.Suite(&StorageConfigSuite{})
26
27func (s *StorageConfigSuite) TestStorageConfig(c *gc.C) {
28 cfg := newStorageConfig(c, validAttrs())
29 cfg.storageDir = "dir"
30 cfg.storageAddr = "addr"
31 cfg.storagePort = 8080
32 c.Check(cfg.StorageDir(), gc.Equals, "dir")
33 c.Check(cfg.StorageAddr(), gc.Equals, "addr:8080")
34 c.Check(cfg.StorageCACert(), gc.Equals, testing.CACert)
35 c.Check(cfg.StorageCAKey(), gc.Equals, testing.CAKey)
36 c.Check(cfg.StorageAuthKey(), gc.Equals, "ABCDEFGH")
37
38 hostnames := cfg.StorageHostnames()
39 c.Assert(hostnames, gc.HasLen, 1)
40 c.Check(hostnames[0], gc.Equals, "addr")
41}
42
43func (s *StorageConfigSuite) TestStorageConfigEmpty(c *gc.C) {
44 cfg := storageConfig{
45 ecfg: &environConfig{
46 Config: &config.Config{},
47 },
48 }
49 c.Check(cfg.StorageDir(), gc.Equals, "")
50 c.Check(cfg.StorageAddr(), gc.Equals, ":0")
51 c.Check(cfg.StorageCACert(), gc.Equals, "")
52 c.Check(cfg.StorageCAKey(), gc.Equals, "")
53 c.Check(cfg.StorageAuthKey(), gc.Equals, "")
54
55 hostnames := cfg.StorageHostnames()
56 c.Assert(hostnames, gc.HasLen, 1)
57 c.Check(hostnames[0], gc.Equals, "")
58}

Subscribers

People subscribed via source and target branches

to status/vote changes: