Left: | ||
Right: |
OLD | NEW |
---|---|
1 package maas | 1 package maas |
2 | 2 |
3 import ( | 3 import ( |
4 "encoding/base64" | 4 "encoding/base64" |
5 "errors" | 5 "errors" |
6 "fmt" | 6 "fmt" |
7 "launchpad.net/gomaasapi" | 7 "launchpad.net/gomaasapi" |
8 "launchpad.net/juju-core/constraints" | 8 "launchpad.net/juju-core/constraints" |
9 "launchpad.net/juju-core/environs" | 9 "launchpad.net/juju-core/environs" |
10 "launchpad.net/juju-core/environs/cloudinit" | 10 "launchpad.net/juju-core/environs/cloudinit" |
11 "launchpad.net/juju-core/environs/config" | 11 "launchpad.net/juju-core/environs/config" |
12 "launchpad.net/juju-core/environs/tools" | |
12 "launchpad.net/juju-core/log" | 13 "launchpad.net/juju-core/log" |
13 "launchpad.net/juju-core/state" | 14 "launchpad.net/juju-core/state" |
14 "launchpad.net/juju-core/state/api" | 15 "launchpad.net/juju-core/state/api" |
15 "launchpad.net/juju-core/state/api/params" | 16 "launchpad.net/juju-core/state/api/params" |
16 "launchpad.net/juju-core/trivial" | 17 "launchpad.net/juju-core/trivial" |
17 "launchpad.net/juju-core/version" | |
18 "net/url" | 18 "net/url" |
19 "sync" | 19 "sync" |
20 "time" | 20 "time" |
21 ) | 21 ) |
22 | 22 |
23 const ( | 23 const ( |
24 mgoPort = 37017 | 24 mgoPort = 37017 |
25 apiPort = 17070 | 25 apiPort = 17070 |
26 jujuDataDir = "/var/lib/juju" | 26 jujuDataDir = "/var/lib/juju" |
27 // We're using v1.0 of the MAAS API. | 27 // We're using v1.0 of the MAAS API. |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
61 return nil, err | 61 return nil, err |
62 } | 62 } |
63 env.storageUnlocked = NewStorage(env) | 63 env.storageUnlocked = NewStorage(env) |
64 return env, nil | 64 return env, nil |
65 } | 65 } |
66 | 66 |
67 func (env *maasEnviron) Name() string { | 67 func (env *maasEnviron) Name() string { |
68 return env.name | 68 return env.name |
69 } | 69 } |
70 | 70 |
71 // TODO: this code is cargo-culted from the openstack/ec2 providers. | |
72 func (env *maasEnviron) findTools() (*state.Tools, error) { | |
73 flags := environs.HighestVersion | environs.CompatVersion | |
74 v := version.Current | |
75 v.Series = env.Config().DefaultSeries() | |
76 return environs.FindTools(env, v, flags) | |
77 } | |
78 | |
79 // makeMachineConfig sets up a basic machine configuration for use with | 71 // makeMachineConfig sets up a basic machine configuration for use with |
80 // userData(). You may still need to supply more information, but this takes | 72 // userData(). You may still need to supply more information, but this takes |
81 // care of the fixed entries and the ones that are always needed. | 73 // care of the fixed entries and the ones that are always needed. |
82 func (env *maasEnviron) makeMachineConfig(machineID, machineNonce string, stateI nfo *state.Info, apiInfo *api.Info, tools *state.Tools) *cloudinit.MachineConfig { | 74 func (env *maasEnviron) makeMachineConfig(machineID, machineNonce string, stateI nfo *state.Info, apiInfo *api.Info) *cloudinit.MachineConfig { |
83 return &cloudinit.MachineConfig{ | 75 return &cloudinit.MachineConfig{ |
84 // Fixed entries. | 76 // Fixed entries. |
85 MongoPort: mgoPort, | 77 MongoPort: mgoPort, |
86 APIPort: apiPort, | 78 APIPort: apiPort, |
87 DataDir: jujuDataDir, | 79 DataDir: jujuDataDir, |
88 | 80 |
89 // Entries based purely on what's in the environment. | |
90 AuthorizedKeys: env.ecfg().AuthorizedKeys(), | |
91 | |
92 // Parameter entries. | 81 // Parameter entries. |
93 MachineId: machineID, | 82 MachineId: machineID, |
94 MachineNonce: machineNonce, | 83 MachineNonce: machineNonce, |
95 StateInfo: stateInfo, | 84 StateInfo: stateInfo, |
96 APIInfo: apiInfo, | 85 APIInfo: apiInfo, |
97 Tools: tools, | |
98 } | 86 } |
99 } | 87 } |
100 | 88 |
101 // startBootstrapNode starts the juju bootstrap node for this environment. | 89 // startBootstrapNode starts the juju bootstrap node for this environment. |
102 func (env *maasEnviron) startBootstrapNode(tools *state.Tools, cert, key []byte, password string) (environs.Instance, error) { | 90 func (env *maasEnviron) startBootstrapNode(cons constraints.Value) (environs.Ins tance, error) { |
103 » config, err := environs.BootstrapConfig(env.Provider(), env.Config(), to ols) | |
104 » if err != nil { | |
105 » » return nil, fmt.Errorf("unable to determine initial configuratio n: %v", err) | |
106 » } | |
107 » caCert, hasCert := env.Config().CACert() | |
108 » if !hasCert { | |
109 » » return nil, fmt.Errorf("no CA certificate in environment configu ration") | |
110 » } | |
111 » stateInfo := state.Info{ | |
112 » » Password: trivial.PasswordHash(password), | |
113 » » CACert: caCert, | |
114 » } | |
115 » apiInfo := api.Info{ | |
116 » » Password: trivial.PasswordHash(password), | |
117 » » CACert: caCert, | |
118 » } | |
119 | |
120 // The bootstrap instance gets machine id "0". This is not related to | 91 // The bootstrap instance gets machine id "0". This is not related to |
121 // instance ids or MAAS system ids. Juju assigns the machine ID. | 92 // instance ids or MAAS system ids. Juju assigns the machine ID. |
122 const machineID = "0" | 93 const machineID = "0" |
94 mcfg := env.makeMachineConfig(machineID, state.BootstrapNonce, nil, nil) | |
95 mcfg.StateServer = true | |
123 | 96 |
124 » mcfg := env.makeMachineConfig(machineID, state.BootstrapNonce, &stateInf o, &apiInfo, tools) | 97 » log.Debugf("environs/maas: bootstrapping environment %q", env.Name()) |
125 » mcfg.StateServer = true | 98 » possibleTools, err := environs.FindBootstrapTools(env, cons) |
126 » mcfg.StateServerCert = cert | 99 » if err != nil { |
127 » mcfg.StateServerKey = key | 100 » » return nil, err |
128 » mcfg.Config = config | 101 » } |
129 | 102 » inst, err := env.obtainNode(machineID, cons, possibleTools, mcfg) |
130 » inst, err := env.obtainNode(machineID, &stateInfo, &apiInfo, tools, mcfg ) | |
131 if err != nil { | 103 if err != nil { |
132 return nil, fmt.Errorf("cannot start bootstrap instance: %v", er r) | 104 return nil, fmt.Errorf("cannot start bootstrap instance: %v", er r) |
133 } | 105 } |
134 return inst, nil | 106 return inst, nil |
135 } | 107 } |
136 | 108 |
137 // Bootstrap is specified in the Environ interface. | 109 // Bootstrap is specified in the Environ interface. |
138 func (env *maasEnviron) Bootstrap(cons constraints.Value, stateServerCert, state ServerKey []byte) error { | 110 func (env *maasEnviron) Bootstrap(cons constraints.Value) error { |
139 » constraints := cons.String() | 111 » // TODO(fwereade): this should check for an existing environment before |
140 » if constraints != "" { | 112 » // starting a new one -- even given raciness, it's better than nothing. |
141 » » log.Warningf("ignoring constraints '%s' (not implemented)", cons traints) | 113 » inst, err := env.startBootstrapNode(cons) |
142 » } | |
143 | |
144 » // This was all cargo-culted from the EC2 provider. | |
145 » password := env.Config().AdminSecret() | |
146 » if password == "" { | |
147 » » return fmt.Errorf("admin-secret is required for bootstrap") | |
148 » } | |
149 » log.Debugf("environs/maas: bootstrapping environment %q.", env.Name()) | |
150 » tools, err := env.findTools() | |
151 » if err != nil { | |
152 » » return err | |
153 » } | |
154 » inst, err := env.startBootstrapNode(tools, stateServerCert, stateServerK ey, password) | |
155 if err != nil { | 114 if err != nil { |
156 return err | 115 return err |
157 } | 116 } |
158 err = env.saveState(&bootstrapState{StateInstances: []state.InstanceId{i nst.Id()}}) | 117 err = env.saveState(&bootstrapState{StateInstances: []state.InstanceId{i nst.Id()}}) |
159 if err != nil { | 118 if err != nil { |
160 » » env.releaseInstance(inst) | 119 » » if err := env.releaseInstance(inst); err != nil { |
120 » » » log.Errorf("cannot release failed bootstrap instance: %v ", err) | |
dimitern
2013/04/15 17:27:23
environs/maas: ...
fwereade
2013/04/16 07:39:13
Done.
| |
121 » » } | |
161 return fmt.Errorf("cannot save state: %v", err) | 122 return fmt.Errorf("cannot save state: %v", err) |
162 } | 123 } |
163 | 124 |
164 // TODO make safe in the case of racing Bootstraps | 125 // TODO make safe in the case of racing Bootstraps |
165 // If two Bootstraps are called concurrently, there's | 126 // If two Bootstraps are called concurrently, there's |
166 // no way to make sure that only one succeeds. | 127 // no way to make sure that only one succeeds. |
167 | |
168 return nil | 128 return nil |
169 } | 129 } |
170 | 130 |
171 // StateInfo is specified in the Environ interface. | 131 // StateInfo is specified in the Environ interface. |
172 func (env *maasEnviron) StateInfo() (*state.Info, *api.Info, error) { | 132 func (env *maasEnviron) StateInfo() (*state.Info, *api.Info, error) { |
173 // This code is cargo-culted from the openstack/ec2 providers. | 133 // This code is cargo-culted from the openstack/ec2 providers. |
174 // It's a bit unclear what the "longAttempt" loop is actually for | 134 // It's a bit unclear what the "longAttempt" loop is actually for |
175 // but this should probably be refactored outside of the provider | 135 // but this should probably be refactored outside of the provider |
176 // code. | 136 // code. |
177 st, err := env.loadState() | 137 st, err := env.loadState() |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
260 authClient, err := gomaasapi.NewAuthenticatedClient(ecfg.MAASServer(), e cfg.MAASOAuth(), apiVersion) | 220 authClient, err := gomaasapi.NewAuthenticatedClient(ecfg.MAASServer(), e cfg.MAASOAuth(), apiVersion) |
261 if err != nil { | 221 if err != nil { |
262 return err | 222 return err |
263 } | 223 } |
264 env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient) | 224 env.maasClientUnlocked = gomaasapi.NewMAAS(*authClient) |
265 | 225 |
266 return nil | 226 return nil |
267 } | 227 } |
268 | 228 |
269 // getMAASClient returns a MAAS client object to use for a request, in a | 229 // getMAASClient returns a MAAS client object to use for a request, in a |
270 // lock-protected fashioon. | 230 // lock-protected fashion. |
271 func (env *maasEnviron) getMAASClient() *gomaasapi.MAASObject { | 231 func (env *maasEnviron) getMAASClient() *gomaasapi.MAASObject { |
272 env.ecfgMutex.Lock() | 232 env.ecfgMutex.Lock() |
273 defer env.ecfgMutex.Unlock() | 233 defer env.ecfgMutex.Unlock() |
274 | 234 |
275 return env.maasClientUnlocked | 235 return env.maasClientUnlocked |
276 } | 236 } |
277 | 237 |
278 // acquireNode allocates a node from the MAAS. | 238 // acquireNode allocates a node from the MAAS. |
279 func (environ *maasEnviron) acquireNode() (gomaasapi.MAASObject, error) { | 239 func (environ *maasEnviron) acquireNode(cons constraints.Value, possibleTools to ols.List) (gomaasapi.MAASObject, *state.Tools, error) { |
240 » log.Warningf("environs/maas: ignoring constraints %q", cons) | |
280 retry := trivial.AttemptStrategy{ | 241 retry := trivial.AttemptStrategy{ |
281 Total: 5 * time.Second, | 242 Total: 5 * time.Second, |
282 Delay: 200 * time.Millisecond, | 243 Delay: 200 * time.Millisecond, |
283 } | 244 } |
284 var result gomaasapi.JSONObject | 245 var result gomaasapi.JSONObject |
285 var err error | 246 var err error |
286 for a := retry.Start(); a.Next(); { | 247 for a := retry.Start(); a.Next(); { |
287 client := environ.getMAASClient().GetSubObject("nodes/") | 248 client := environ.getMAASClient().GetSubObject("nodes/") |
288 result, err = client.CallPost("acquire", nil) | 249 result, err = client.CallPost("acquire", nil) |
289 if err == nil { | 250 if err == nil { |
290 break | 251 break |
291 } | 252 } |
292 } | 253 } |
293 if err != nil { | 254 if err != nil { |
294 » » return gomaasapi.MAASObject{}, err | 255 » » return gomaasapi.MAASObject{}, nil, err |
295 } | 256 } |
296 node, err := result.GetMAASObject() | 257 node, err := result.GetMAASObject() |
297 if err != nil { | 258 if err != nil { |
298 msg := fmt.Errorf("unexpected result from 'acquire' on MAAS API: %v", err) | 259 msg := fmt.Errorf("unexpected result from 'acquire' on MAAS API: %v", err) |
299 » » return gomaasapi.MAASObject{}, msg | 260 » » return gomaasapi.MAASObject{}, nil, msg |
300 } | 261 } |
301 » return node, nil | 262 » tools := possibleTools[0] |
263 » log.Warningf("environs/maas: picked arbitrary tools %q", tools) | |
264 » return node, tools, nil | |
302 } | 265 } |
303 | 266 |
304 // startNode installs and boots a node. | 267 // startNode installs and boots a node. |
305 func (environ *maasEnviron) startNode(node gomaasapi.MAASObject, tools *state.To ols, userdata []byte) error { | 268 func (environ *maasEnviron) startNode(node gomaasapi.MAASObject, series string, userdata []byte) error { |
306 retry := trivial.AttemptStrategy{ | 269 retry := trivial.AttemptStrategy{ |
307 Total: 5 * time.Second, | 270 Total: 5 * time.Second, |
308 Delay: 200 * time.Millisecond, | 271 Delay: 200 * time.Millisecond, |
309 } | 272 } |
310 userDataParam := base64.StdEncoding.EncodeToString(userdata) | 273 userDataParam := base64.StdEncoding.EncodeToString(userdata) |
311 params := url.Values{ | 274 params := url.Values{ |
312 » » "distro_series": {tools.Series}, | 275 » » "distro_series": {series}, |
313 "user_data": {userDataParam}, | 276 "user_data": {userDataParam}, |
314 } | 277 } |
315 // Initialize err to a non-nil value as a sentinel for the following | 278 // Initialize err to a non-nil value as a sentinel for the following |
316 // loop. | 279 // loop. |
317 err := fmt.Errorf("(no error)") | 280 err := fmt.Errorf("(no error)") |
318 for a := retry.Start(); a.Next() && err != nil; { | 281 for a := retry.Start(); a.Next() && err != nil; { |
319 _, err = node.CallPost("start", params) | 282 _, err = node.CallPost("start", params) |
320 } | 283 } |
321 return err | 284 return err |
322 } | 285 } |
323 | 286 |
324 // obtainNode allocates and starts a MAAS node. It is used both for the | 287 // obtainNode allocates and starts a MAAS node. It is used both for the |
325 // implementation of StartInstance, and to initialize the bootstrap node. | 288 // implementation of StartInstance, and to initialize the bootstrap node. |
326 func (environ *maasEnviron) obtainNode(machineId string, stateInfo *state.Info, apiInfo *api.Info, tools *state.Tools, mcfg *cloudinit.MachineConfig) (*maasInst ance, error) { | 289 func (environ *maasEnviron) obtainNode(machineId string, cons constraints.Value, possibleTools tools.List, mcfg *cloudinit.MachineConfig) (_ *maasInstance, err error) { |
290 » series := possibleTools.Series() | |
291 » if len(series) != 1 { | |
rog
2013/04/15 13:24:10
please no panic
fwereade
2013/04/16 07:39:13
Same waffling applies. Done.
| |
292 » » log.Errorf("expected one series; got %v", series) | |
dimitern
2013/04/15 17:27:23
environs/maas: ...
| |
293 » » panic("series should have been chosen by now") | |
294 » } | |
295 » var instance *maasInstance | |
296 » if node, tools, err := environ.acquireNode(cons, possibleTools); err != nil { | |
297 » » return nil, fmt.Errorf("cannot run instances: %v", err) | |
298 » } else { | |
299 » » instance = &maasInstance{&node, environ} | |
300 » » mcfg.Tools = tools | |
301 » } | |
302 » defer func() { | |
303 » » if err != nil { | |
304 » » » if err := environ.releaseInstance(instance); err != nil { | |
dimitern
2013/04/15 17:27:23
you're losing the original error here, isn't that
fwereade
2013/04/16 07:39:13
Am I? http://play.golang.org/
| |
305 » » » » log.Errorf("error releasing failed instance: %v" , err) | |
dimitern
2013/04/15 17:27:23
environs/maas: ...
fwereade
2013/04/16 07:39:13
Done.
| |
306 » » » } | |
307 » » } | |
308 » }() | |
327 | 309 |
328 » log.Debugf("environs/maas: starting machine %s in $q running tools versi on %q from %q", machineId, environ.name, tools.Binary, tools.URL) | 310 » hostname, err := instance.DNSName() |
329 | |
330 » node, err := environ.acquireNode() | |
331 » if err != nil { | |
332 » » return nil, fmt.Errorf("cannot run instances: %v", err) | |
333 » } | |
334 | |
335 » hostname, err := node.GetField("hostname") | |
336 if err != nil { | 311 if err != nil { |
337 return nil, err | 312 return nil, err |
338 } | 313 } |
339 instance := maasInstance{&node, environ} | |
340 info := machineInfo{string(instance.Id()), hostname} | 314 info := machineInfo{string(instance.Id()), hostname} |
341 runCmd, err := info.cloudinitRunCmd() | 315 runCmd, err := info.cloudinitRunCmd() |
342 if err != nil { | 316 if err != nil { |
343 return nil, err | 317 return nil, err |
344 } | 318 } |
319 if err := environs.FinishMachineConfig(mcfg, environ.Config(), cons); er r != nil { | |
320 return nil, err | |
321 } | |
345 userdata, err := userData(mcfg, runCmd) | 322 userdata, err := userData(mcfg, runCmd) |
346 if err != nil { | 323 if err != nil { |
347 msg := fmt.Errorf("could not compose userdata for bootstrap node : %v", err) | 324 msg := fmt.Errorf("could not compose userdata for bootstrap node : %v", err) |
348 return nil, msg | 325 return nil, msg |
349 } | 326 } |
350 » err = environ.startNode(node, tools, userdata) | 327 » if err := environ.startNode(*instance.maasObject, series[0], userdata); err != nil { |
351 » if err != nil { | 328 » » return nil, err |
352 » » environ.releaseInstance(&instance) | |
353 » » return nil, fmt.Errorf("cannot start instance: %v", err) | |
354 } | 329 } |
355 log.Debugf("environs/maas: started instance %q", instance.Id()) | 330 log.Debugf("environs/maas: started instance %q", instance.Id()) |
356 » return &instance, nil | 331 » return instance, nil |
357 } | 332 } |
358 | 333 |
359 // StartInstance is specified in the Environ interface. | 334 // StartInstance is specified in the Environ interface. |
360 func (environ *maasEnviron) StartInstance(machineID, machineNonce string, series string, cons constraints.Value, stateInfo *state.Info, apiInfo *api.Info) (envi rons.Instance, error) { | 335 func (environ *maasEnviron) StartInstance(machineID, machineNonce string, series string, cons constraints.Value, stateInfo *state.Info, apiInfo *api.Info) (envi rons.Instance, error) { |
361 » // TODO: Support series and constraints. They were added to the | 336 » possibleTools, err := environs.FindInstanceTools(environ, series, cons) |
362 » // interface after we implemented. | |
363 » flags := environs.HighestVersion | environs.CompatVersion | |
364 » var err error | |
365 » tools, err := environs.FindTools(environ, version.Current, flags) | |
366 if err != nil { | 337 if err != nil { |
367 return nil, err | 338 return nil, err |
368 } | 339 } |
369 | 340 » mcfg := environ.makeMachineConfig(machineID, machineNonce, stateInfo, ap iInfo) |
370 » mcfg := environ.makeMachineConfig(machineID, machineNonce, stateInfo, ap iInfo, tools) | 341 » return environ.obtainNode(machineID, cons, possibleTools, mcfg) |
371 » return environ.obtainNode(machineID, stateInfo, apiInfo, tools, mcfg) | |
372 } | 342 } |
373 | 343 |
374 // StopInstances is specified in the Environ interface. | 344 // StopInstances is specified in the Environ interface. |
375 func (environ *maasEnviron) StopInstances(instances []environs.Instance) error { | 345 func (environ *maasEnviron) StopInstances(instances []environs.Instance) error { |
376 // Shortcut to exit quickly if 'instances' is an empty slice or nil. | 346 // Shortcut to exit quickly if 'instances' is an empty slice or nil. |
377 if len(instances) == 0 { | 347 if len(instances) == 0 { |
378 return nil | 348 return nil |
379 } | 349 } |
380 // Tell MAAS to release each of the instances. If there are errors, | 350 // Tell MAAS to release each of the instances. If there are errors, |
381 // return only the first one (but release all instances regardless). | 351 // return only the first one (but release all instances regardless). |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
510 } | 480 } |
511 | 481 |
512 func (*maasEnviron) Ports() ([]params.Port, error) { | 482 func (*maasEnviron) Ports() ([]params.Port, error) { |
513 log.Debugf("environs/maas: unimplemented Ports() called") | 483 log.Debugf("environs/maas: unimplemented Ports() called") |
514 return []params.Port{}, nil | 484 return []params.Port{}, nil |
515 } | 485 } |
516 | 486 |
517 func (*maasEnviron) Provider() environs.EnvironProvider { | 487 func (*maasEnviron) Provider() environs.EnvironProvider { |
518 return &providerInstance | 488 return &providerInstance |
519 } | 489 } |
OLD | NEW |