~rogpeppe/juju-core/azure

« back to all changes in this revision

Viewing changes to state/machine.go

  • Committer: Roger Peppe
  • Date: 2011-12-07 17:03:34 UTC
  • mto: (25.3.4 go-trunk)
  • mto: This revision was merged to the branch mainline in revision 27.
  • Revision ID: roger.peppe@canonical.com-20111207170334-soasb88g2x5mpkf5
add cloudinit package

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
// Copyright 2012, 2013 Canonical Ltd.
2
 
// Licensed under the AGPLv3, see LICENCE file for details.
3
 
 
4
 
package state
5
 
 
6
 
import (
7
 
        "fmt"
8
 
        "strings"
9
 
        "time"
10
 
 
11
 
        "labix.org/v2/mgo"
12
 
        "labix.org/v2/mgo/txn"
13
 
 
14
 
        "launchpad.net/juju-core/constraints"
15
 
        "launchpad.net/juju-core/errors"
16
 
        "launchpad.net/juju-core/instance"
17
 
        "launchpad.net/juju-core/state/api/params"
18
 
        "launchpad.net/juju-core/state/presence"
19
 
        "launchpad.net/juju-core/utils"
20
 
)
21
 
 
22
 
// Machine represents the state of a machine.
23
 
type Machine struct {
24
 
        st  *State
25
 
        doc machineDoc
26
 
        annotator
27
 
}
28
 
 
29
 
// MachineJob values define responsibilities that machines may be
30
 
// expected to fulfil.
31
 
type MachineJob int
32
 
 
33
 
const (
34
 
        _ MachineJob = iota
35
 
        JobHostUnits
36
 
        JobManageEnviron
37
 
        JobManageState
38
 
)
39
 
 
40
 
var jobNames = []params.MachineJob{
41
 
        JobHostUnits:     params.JobHostUnits,
42
 
        JobManageEnviron: params.JobManageEnviron,
43
 
        JobManageState:   params.JobManageState,
44
 
}
45
 
 
46
 
func (job MachineJob) String() string {
47
 
        j := int(job)
48
 
        if j <= 0 || j >= len(jobNames) {
49
 
                return fmt.Sprintf("<unknown job %d>", j)
50
 
        }
51
 
        return string(jobNames[j])
52
 
}
53
 
 
54
 
// machineDoc represents the internal state of a machine in MongoDB.
55
 
// Note the correspondence with MachineInfo in state/api/params.
56
 
type machineDoc struct {
57
 
        Id            string `bson:"_id"`
58
 
        Nonce         string
59
 
        Series        string
60
 
        ContainerType string
61
 
        Principals    []string
62
 
        Life          Life
63
 
        Tools         *Tools `bson:",omitempty"`
64
 
        TxnRevno      int64  `bson:"txn-revno"`
65
 
        Jobs          []MachineJob
66
 
        PasswordHash  string
67
 
        Clean         bool
68
 
        // Deprecated. InstanceId, now lives on instanceData.
69
 
        // This attribute is retained so that data from existing machines can be read.
70
 
        // SCHEMACHANGE
71
 
        // TODO(wallyworld): remove this attribute when schema upgrades are possible.
72
 
        InstanceId instance.Id
73
 
}
74
 
 
75
 
func newMachine(st *State, doc *machineDoc) *Machine {
76
 
        machine := &Machine{
77
 
                st:  st,
78
 
                doc: *doc,
79
 
        }
80
 
        machine.annotator = annotator{
81
 
                globalKey: machine.globalKey(),
82
 
                tag:       machine.Tag(),
83
 
                st:        st,
84
 
        }
85
 
        return machine
86
 
}
87
 
 
88
 
// Id returns the machine id.
89
 
func (m *Machine) Id() string {
90
 
        return m.doc.Id
91
 
}
92
 
 
93
 
// Series returns the operating system series running on the machine.
94
 
func (m *Machine) Series() string {
95
 
        return m.doc.Series
96
 
}
97
 
 
98
 
// ContainerType returns the type of container hosting this machine.
99
 
func (m *Machine) ContainerType() instance.ContainerType {
100
 
        return instance.ContainerType(m.doc.ContainerType)
101
 
}
102
 
 
103
 
// machineGlobalKey returns the global database key for the identified machine.
104
 
func machineGlobalKey(id string) string {
105
 
        return "m#" + id
106
 
}
107
 
 
108
 
// globalKey returns the global database key for the machine.
109
 
func (m *Machine) globalKey() string {
110
 
        return machineGlobalKey(m.doc.Id)
111
 
}
112
 
 
113
 
// instanceData holds attributes relevant to a provisioned machine.
114
 
type instanceData struct {
115
 
        Id         string      `bson:"_id"`
116
 
        InstanceId instance.Id `bson:"instanceid"`
117
 
        Arch       *string     `bson:"arch,omitempty"`
118
 
        Mem        *uint64     `bson:"mem,omitempty"`
119
 
        CpuCores   *uint64     `bson:"cpucores,omitempty"`
120
 
        CpuPower   *uint64     `bson:"cpupower,omitempty"`
121
 
        TxnRevno   int64       `bson:"txn-revno"`
122
 
}
123
 
 
124
 
// TODO(wallyworld): move this method to a service.
125
 
func (m *Machine) HardwareCharacteristics() (*instance.HardwareCharacteristics, error) {
126
 
        hc := &instance.HardwareCharacteristics{}
127
 
        instData, err := getInstanceData(m.st, m.Id())
128
 
        if err != nil {
129
 
                return nil, err
130
 
        }
131
 
        hc.Arch = instData.Arch
132
 
        hc.Mem = instData.Mem
133
 
        hc.CpuCores = instData.CpuCores
134
 
        hc.CpuPower = instData.CpuPower
135
 
        return hc, nil
136
 
}
137
 
 
138
 
func getInstanceData(st *State, id string) (instanceData, error) {
139
 
        var instData instanceData
140
 
        err := st.instanceData.FindId(id).One(&instData)
141
 
        if err == mgo.ErrNotFound {
142
 
                return instanceData{}, errors.NotFoundf("instance data for machine %v", id)
143
 
        }
144
 
        if err != nil {
145
 
                return instanceData{}, fmt.Errorf("cannot get instance data for machine %v: %v", id, err)
146
 
        }
147
 
        return instData, nil
148
 
}
149
 
 
150
 
const machineTagPrefix = "machine-"
151
 
 
152
 
// MachineTag returns the tag for the
153
 
// machine with the given id.
154
 
func MachineTag(id string) string {
155
 
        tag := fmt.Sprintf("%s%s", machineTagPrefix, id)
156
 
        // Containers require "/" to be replaced by "-".
157
 
        tag = strings.Replace(tag, "/", "-", -1)
158
 
        return tag
159
 
}
160
 
 
161
 
// MachineIdFromTag returns the machine id that was used to create the tag.
162
 
func MachineIdFromTag(tag string) string {
163
 
        // TODO(dimitern): Possibly change this to return (string, error),
164
 
        // so the case below can be reported.
165
 
        if !strings.HasPrefix(tag, machineTagPrefix) {
166
 
                return ""
167
 
        }
168
 
        // Strip off the "machine-" prefix.
169
 
        id := tag[len(machineTagPrefix):]
170
 
        // Put the slashes back.
171
 
        id = strings.Replace(id, "-", "/", -1)
172
 
        return id
173
 
}
174
 
 
175
 
// Tag returns a name identifying the machine that is safe to use
176
 
// as a file name.  The returned name will be different from other
177
 
// Tag values returned by any other entities from the same state.
178
 
func (m *Machine) Tag() string {
179
 
        return MachineTag(m.Id())
180
 
}
181
 
 
182
 
// Life returns whether the machine is Alive, Dying or Dead.
183
 
func (m *Machine) Life() Life {
184
 
        return m.doc.Life
185
 
}
186
 
 
187
 
// Jobs returns the responsibilities that must be fulfilled by m's agent.
188
 
func (m *Machine) Jobs() []MachineJob {
189
 
        return m.doc.Jobs
190
 
}
191
 
 
192
 
// AgentTools returns the tools that the agent is currently running.
193
 
// It returns an error that satisfies IsNotFound if the tools have not yet been set.
194
 
func (m *Machine) AgentTools() (*Tools, error) {
195
 
        if m.doc.Tools == nil {
196
 
                return nil, errors.NotFoundf("agent tools for machine %v", m)
197
 
        }
198
 
        tools := *m.doc.Tools
199
 
        return &tools, nil
200
 
}
201
 
 
202
 
// SetAgentTools sets the tools that the agent is currently running.
203
 
func (m *Machine) SetAgentTools(t *Tools) (err error) {
204
 
        defer utils.ErrorContextf(&err, "cannot set agent tools for machine %v", m)
205
 
        if t.Series == "" || t.Arch == "" {
206
 
                return fmt.Errorf("empty series or arch")
207
 
        }
208
 
        ops := []txn.Op{{
209
 
                C:      m.st.machines.Name,
210
 
                Id:     m.doc.Id,
211
 
                Assert: notDeadDoc,
212
 
                Update: D{{"$set", D{{"tools", t}}}},
213
 
        }}
214
 
        if err := m.st.runTransaction(ops); err != nil {
215
 
                return onAbort(err, errDead)
216
 
        }
217
 
        tools := *t
218
 
        m.doc.Tools = &tools
219
 
        return nil
220
 
}
221
 
 
222
 
// SetMongoPassword sets the password the agent responsible for the machine
223
 
// should use to communicate with the state servers.  Previous passwords
224
 
// are invalidated.
225
 
func (m *Machine) SetMongoPassword(password string) error {
226
 
        return m.st.setMongoPassword(m.Tag(), password)
227
 
}
228
 
 
229
 
// SetPassword sets the password for the machine's agent.
230
 
func (m *Machine) SetPassword(password string) error {
231
 
        hp := utils.PasswordHash(password)
232
 
        ops := []txn.Op{{
233
 
                C:      m.st.machines.Name,
234
 
                Id:     m.doc.Id,
235
 
                Assert: notDeadDoc,
236
 
                Update: D{{"$set", D{{"passwordhash", hp}}}},
237
 
        }}
238
 
        if err := m.st.runTransaction(ops); err != nil {
239
 
                return fmt.Errorf("cannot set password of machine %v: %v", m, onAbort(err, errDead))
240
 
        }
241
 
        m.doc.PasswordHash = hp
242
 
        return nil
243
 
}
244
 
 
245
 
// PasswordValid returns whether the given password is valid
246
 
// for the given machine.
247
 
func (m *Machine) PasswordValid(password string) bool {
248
 
        return utils.PasswordHash(password) == m.doc.PasswordHash
249
 
}
250
 
 
251
 
// Destroy sets the machine lifecycle to Dying if it is Alive. It does
252
 
// nothing otherwise. Destroy will fail if the machine has principal
253
 
// units assigned, or if the machine has JobManageEnviron.
254
 
// If the machine has assigned units, Destroy will return
255
 
// a HasAssignedUnitsError.
256
 
func (m *Machine) Destroy() error {
257
 
        return m.advanceLifecycle(Dying)
258
 
}
259
 
 
260
 
// EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying.
261
 
// It does nothing otherwise. EnsureDead will fail if the machine has
262
 
// principal units assigned, or if the machine has JobManageEnviron.
263
 
// If the machine has assigned units, EnsureDead will return
264
 
// a HasAssignedUnitsError.
265
 
func (m *Machine) EnsureDead() error {
266
 
        return m.advanceLifecycle(Dead)
267
 
}
268
 
 
269
 
type HasAssignedUnitsError struct {
270
 
        MachineId string
271
 
        UnitNames []string
272
 
}
273
 
 
274
 
func (e *HasAssignedUnitsError) Error() string {
275
 
        return fmt.Sprintf("machine %s has unit %q assigned", e.MachineId, e.UnitNames[0])
276
 
}
277
 
 
278
 
func IsHasAssignedUnitsError(err error) bool {
279
 
        _, ok := err.(*HasAssignedUnitsError)
280
 
        return ok
281
 
}
282
 
 
283
 
// Containers returns the container ids belonging to a parent machine.
284
 
// TODO(wallyworld): move this method to a service
285
 
func (m *Machine) Containers() ([]string, error) {
286
 
        var mc machineContainers
287
 
        err := m.st.containerRefs.FindId(m.Id()).One(&mc)
288
 
        if err == nil {
289
 
                return mc.Children, nil
290
 
        }
291
 
        if err == mgo.ErrNotFound {
292
 
                return nil, errors.NotFoundf("container info for machine %v", m.Id())
293
 
        }
294
 
        return nil, err
295
 
}
296
 
 
297
 
// ParentId returns the Id of the host machine if this machine is a container.
298
 
func (m *Machine) ParentId() (string, bool) {
299
 
        parentId := ParentId(m.Id())
300
 
        return parentId, parentId != ""
301
 
}
302
 
 
303
 
type HasContainersError struct {
304
 
        MachineId    string
305
 
        ContainerIds []string
306
 
}
307
 
 
308
 
func (e *HasContainersError) Error() string {
309
 
        return fmt.Sprintf("machine %s is hosting containers %q", e.MachineId, strings.Join(e.ContainerIds, ","))
310
 
}
311
 
 
312
 
func IsHasContainersError(err error) bool {
313
 
        _, ok := err.(*HasContainersError)
314
 
        return ok
315
 
}
316
 
 
317
 
// advanceLifecycle ensures that the machine's lifecycle is no earlier
318
 
// than the supplied value. If the machine already has that lifecycle
319
 
// value, or a later one, no changes will be made to remote state. If
320
 
// the machine has any responsibilities that preclude a valid change in
321
 
// lifecycle, it will return an error.
322
 
func (original *Machine) advanceLifecycle(life Life) (err error) {
323
 
        containers, err := original.Containers()
324
 
        if err != nil {
325
 
                return err
326
 
        }
327
 
        if len(containers) > 0 {
328
 
                return &HasContainersError{
329
 
                        MachineId:    original.doc.Id,
330
 
                        ContainerIds: containers,
331
 
                }
332
 
        }
333
 
        m := original
334
 
        defer func() {
335
 
                if err == nil {
336
 
                        // The machine's lifecycle is known to have advanced; it may be
337
 
                        // known to have already advanced further than requested, in
338
 
                        // which case we set the latest known valid value.
339
 
                        if m == nil {
340
 
                                life = Dead
341
 
                        } else if m.doc.Life > life {
342
 
                                life = m.doc.Life
343
 
                        }
344
 
                        original.doc.Life = life
345
 
                }
346
 
        }()
347
 
        // op and
348
 
        op := txn.Op{
349
 
                C:      m.st.machines.Name,
350
 
                Id:     m.doc.Id,
351
 
                Update: D{{"$set", D{{"life", life}}}},
352
 
        }
353
 
        advanceAsserts := D{
354
 
                {"jobs", D{{"$nin", []MachineJob{JobManageEnviron}}}},
355
 
                {"$or", []D{
356
 
                        {{"principals", D{{"$size", 0}}}},
357
 
                        {{"principals", D{{"$exists", false}}}},
358
 
                }},
359
 
        }
360
 
        // 3 attempts: one with original data, one with refreshed data, and a final
361
 
        // one intended to determine the cause of failure of the preceding attempt.
362
 
        for i := 0; i < 3; i++ {
363
 
                // If the transaction was aborted, grab a fresh copy of the machine data.
364
 
                // We don't write to original, because the expectation is that state-
365
 
                // changing methods only set the requested change on the receiver; a case
366
 
                // could perhaps be made that this is not a helpful convention in the
367
 
                // context of the new state API, but we maintain consistency in the
368
 
                // face of uncertainty.
369
 
                if i != 0 {
370
 
                        if m, err = m.st.Machine(m.doc.Id); errors.IsNotFoundError(err) {
371
 
                                return nil
372
 
                        } else if err != nil {
373
 
                                return err
374
 
                        }
375
 
                }
376
 
                // Check that the life change is sane, and collect the assertions
377
 
                // necessary to determine that it remains so.
378
 
                switch life {
379
 
                case Dying:
380
 
                        if m.doc.Life != Alive {
381
 
                                return nil
382
 
                        }
383
 
                        op.Assert = append(advanceAsserts, isAliveDoc...)
384
 
                case Dead:
385
 
                        if m.doc.Life == Dead {
386
 
                                return nil
387
 
                        }
388
 
                        op.Assert = append(advanceAsserts, notDeadDoc...)
389
 
                default:
390
 
                        panic(fmt.Errorf("cannot advance lifecycle to %v", life))
391
 
                }
392
 
                // Check that the machine does not have any responsibilities that
393
 
                // prevent a lifecycle change.
394
 
                for _, j := range m.doc.Jobs {
395
 
                        if j == JobManageEnviron {
396
 
                                // (NOTE: When we enable multiple JobManageEnviron machines,
397
 
                                // the restriction will become "there must be at least one
398
 
                                // machine with this job".)
399
 
                                return fmt.Errorf("machine %s is required by the environment", m.doc.Id)
400
 
                        }
401
 
                }
402
 
                if len(m.doc.Principals) != 0 {
403
 
                        return &HasAssignedUnitsError{
404
 
                                MachineId: m.doc.Id,
405
 
                                UnitNames: m.doc.Principals,
406
 
                        }
407
 
                }
408
 
                // Run the transaction...
409
 
                if err := m.st.runTransaction([]txn.Op{op}); err != txn.ErrAborted {
410
 
                        return err
411
 
                }
412
 
                // ...and retry on abort.
413
 
        }
414
 
        // In very rare circumstances, the final iteration above will have determined
415
 
        // no cause of failure, and attempted a final transaction: if this also failed,
416
 
        // we can be sure that the machine document is changing very fast, in a somewhat
417
 
        // surprising fashion, and that it is sensible to back off for now.
418
 
        return fmt.Errorf("machine %s cannot advance lifecycle: %v", m, ErrExcessiveContention)
419
 
}
420
 
 
421
 
// Remove removes the machine from state. It will fail if the machine is not
422
 
// Dead.
423
 
func (m *Machine) Remove() (err error) {
424
 
        defer utils.ErrorContextf(&err, "cannot remove machine %s", m.doc.Id)
425
 
        if m.doc.Life != Dead {
426
 
                return fmt.Errorf("machine is not dead")
427
 
        }
428
 
        ops := []txn.Op{
429
 
                {
430
 
                        C:      m.st.machines.Name,
431
 
                        Id:     m.doc.Id,
432
 
                        Assert: txn.DocExists,
433
 
                        Remove: true,
434
 
                },
435
 
                {
436
 
                        C:      m.st.instanceData.Name,
437
 
                        Id:     m.doc.Id,
438
 
                        Remove: true,
439
 
                },
440
 
                removeStatusOp(m.st, m.globalKey()),
441
 
                removeConstraintsOp(m.st, m.globalKey()),
442
 
                annotationRemoveOp(m.st, m.globalKey()),
443
 
        }
444
 
        ops = append(ops, removeContainerRefOps(m.st, m.Id())...)
445
 
        // The only abort conditions in play indicate that the machine has already
446
 
        // been removed.
447
 
        return onAbort(m.st.runTransaction(ops), nil)
448
 
}
449
 
 
450
 
// Refresh refreshes the contents of the machine from the underlying
451
 
// state. It returns an error that satisfies IsNotFound if the machine has
452
 
// been removed.
453
 
func (m *Machine) Refresh() error {
454
 
        doc := machineDoc{}
455
 
        err := m.st.machines.FindId(m.doc.Id).One(&doc)
456
 
        if err == mgo.ErrNotFound {
457
 
                return errors.NotFoundf("machine %v", m)
458
 
        }
459
 
        if err != nil {
460
 
                return fmt.Errorf("cannot refresh machine %v: %v", m, err)
461
 
        }
462
 
        m.doc = doc
463
 
        return nil
464
 
}
465
 
 
466
 
// AgentAlive returns whether the respective remote agent is alive.
467
 
func (m *Machine) AgentAlive() (bool, error) {
468
 
        return m.st.pwatcher.Alive(m.globalKey())
469
 
}
470
 
 
471
 
// WaitAgentAlive blocks until the respective agent is alive.
472
 
func (m *Machine) WaitAgentAlive(timeout time.Duration) (err error) {
473
 
        defer utils.ErrorContextf(&err, "waiting for agent of machine %v", m)
474
 
        ch := make(chan presence.Change)
475
 
        m.st.pwatcher.Watch(m.globalKey(), ch)
476
 
        defer m.st.pwatcher.Unwatch(m.globalKey(), ch)
477
 
        for i := 0; i < 2; i++ {
478
 
                select {
479
 
                case change := <-ch:
480
 
                        if change.Alive {
481
 
                                return nil
482
 
                        }
483
 
                case <-time.After(timeout):
484
 
                        return fmt.Errorf("still not alive after timeout")
485
 
                case <-m.st.pwatcher.Dead():
486
 
                        return m.st.pwatcher.Err()
487
 
                }
488
 
        }
489
 
        panic(fmt.Sprintf("presence reported dead status twice in a row for machine %v", m))
490
 
}
491
 
 
492
 
// SetAgentAlive signals that the agent for machine m is alive.
493
 
// It returns the started pinger.
494
 
func (m *Machine) SetAgentAlive() (*presence.Pinger, error) {
495
 
        p := presence.NewPinger(m.st.presence, m.globalKey())
496
 
        err := p.Start()
497
 
        if err != nil {
498
 
                return nil, err
499
 
        }
500
 
        return p, nil
501
 
}
502
 
 
503
 
// InstanceId returns the provider specific instance id for this machine
504
 
// and whether it has been set.
505
 
func (m *Machine) InstanceId() (instance.Id, error) {
506
 
        // SCHEMACHANGE
507
 
        // TODO(wallyworld) - remove this backward compatibility code when schema upgrades are possible
508
 
        // (we first check for InstanceId stored on the machineDoc)
509
 
        if m.doc.InstanceId != "" {
510
 
                return m.doc.InstanceId, nil
511
 
        }
512
 
        instData, err := getInstanceData(m.st, m.Id())
513
 
        if (err == nil && instData.InstanceId == "") || (err != nil && errors.IsNotFoundError(err)) {
514
 
                err = &NotProvisionedError{m.Id()}
515
 
        }
516
 
        if err != nil {
517
 
                return "", err
518
 
        }
519
 
        return instData.InstanceId, nil
520
 
}
521
 
 
522
 
// Units returns all the units that have been assigned to the machine.
523
 
func (m *Machine) Units() (units []*Unit, err error) {
524
 
        defer utils.ErrorContextf(&err, "cannot get units assigned to machine %v", m)
525
 
        pudocs := []unitDoc{}
526
 
        err = m.st.units.Find(D{{"machineid", m.doc.Id}}).All(&pudocs)
527
 
        if err != nil {
528
 
                return nil, err
529
 
        }
530
 
        for _, pudoc := range pudocs {
531
 
                units = append(units, newUnit(m.st, &pudoc))
532
 
                docs := []unitDoc{}
533
 
                err = m.st.units.Find(D{{"principal", pudoc.Name}}).All(&docs)
534
 
                if err != nil {
535
 
                        return nil, err
536
 
                }
537
 
                for _, doc := range docs {
538
 
                        units = append(units, newUnit(m.st, &doc))
539
 
                }
540
 
        }
541
 
        return units, nil
542
 
}
543
 
 
544
 
// SetProvisioned sets the provider specific machine id, nonce and also metadata for
545
 
// this machine. Once set, the instance id cannot be changed.
546
 
func (m *Machine) SetProvisioned(id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics) (err error) {
547
 
        defer utils.ErrorContextf(&err, "cannot set instance data for machine %q", m)
548
 
 
549
 
        if id == "" || nonce == "" {
550
 
                return fmt.Errorf("instance id and nonce cannot be empty")
551
 
        }
552
 
 
553
 
        if characteristics == nil {
554
 
                characteristics = &instance.HardwareCharacteristics{}
555
 
        }
556
 
        hc := &instanceData{
557
 
                Id:         m.doc.Id,
558
 
                InstanceId: id,
559
 
                Arch:       characteristics.Arch,
560
 
                Mem:        characteristics.Mem,
561
 
                CpuCores:   characteristics.CpuCores,
562
 
                CpuPower:   characteristics.CpuPower,
563
 
        }
564
 
        // SCHEMACHANGE
565
 
        // TODO(wallyworld) - do not check instanceId on machineDoc after schema is upgraded
566
 
        notSetYet := D{{"instanceid", ""}, {"nonce", ""}}
567
 
        ops := []txn.Op{
568
 
                {
569
 
                        C:      m.st.machines.Name,
570
 
                        Id:     m.doc.Id,
571
 
                        Assert: append(isAliveDoc, notSetYet...),
572
 
                        Update: D{{"$set", D{{"instanceid", id}, {"nonce", nonce}}}},
573
 
                }, {
574
 
                        C:      m.st.instanceData.Name,
575
 
                        Id:     m.doc.Id,
576
 
                        Assert: txn.DocMissing,
577
 
                        Insert: hc,
578
 
                },
579
 
        }
580
 
 
581
 
        if err = m.st.runTransaction(ops); err == nil {
582
 
                m.doc.Nonce = nonce
583
 
                // SCHEMACHANGE
584
 
                // TODO(wallyworld) - remove this backward compatibility code when schema upgrades are possible
585
 
                // (InstanceId is stored on the instanceData document but we duplicate the value on the machineDoc.
586
 
                m.doc.InstanceId = id
587
 
                return nil
588
 
        } else if err != txn.ErrAborted {
589
 
                return err
590
 
        } else if alive, err := isAlive(m.st.machines, m.doc.Id); err != nil {
591
 
                return err
592
 
        } else if !alive {
593
 
                return errNotAlive
594
 
        }
595
 
        return fmt.Errorf("already set")
596
 
}
597
 
 
598
 
// NotProvisionedError records an error when a machine is not provisioned.
599
 
type NotProvisionedError struct {
600
 
        machineId string
601
 
}
602
 
 
603
 
// IsNotProvisionedError returns true if err is a NotProvisionedError.
604
 
func IsNotProvisionedError(err error) bool {
605
 
        if _, ok := err.(*NotProvisionedError); ok {
606
 
                return true
607
 
        }
608
 
        return false
609
 
}
610
 
 
611
 
func (e *NotProvisionedError) Error() string {
612
 
        return fmt.Sprintf("machine %v is not provisioned", e.machineId)
613
 
}
614
 
 
615
 
// CheckProvisioned returns true if the machine was provisioned with the given nonce.
616
 
func (m *Machine) CheckProvisioned(nonce string) bool {
617
 
        return nonce == m.doc.Nonce && nonce != ""
618
 
}
619
 
 
620
 
// String returns a unique description of this machine.
621
 
func (m *Machine) String() string {
622
 
        return m.doc.Id
623
 
}
624
 
 
625
 
// Constraints returns the exact constraints that should apply when provisioning
626
 
// an instance for the machine.
627
 
func (m *Machine) Constraints() (constraints.Value, error) {
628
 
        return readConstraints(m.st, m.globalKey())
629
 
}
630
 
 
631
 
// SetConstraints sets the exact constraints to apply when provisioning an
632
 
// instance for the machine. It will fail if the machine is Dead, or if it
633
 
// is already provisioned.
634
 
func (m *Machine) SetConstraints(cons constraints.Value) (err error) {
635
 
        defer utils.ErrorContextf(&err, "cannot set constraints")
636
 
        notSetYet := D{{"nonce", ""}}
637
 
        ops := []txn.Op{
638
 
                {
639
 
                        C:      m.st.machines.Name,
640
 
                        Id:     m.doc.Id,
641
 
                        Assert: append(isAliveDoc, notSetYet...),
642
 
                },
643
 
                setConstraintsOp(m.st, m.globalKey(), cons),
644
 
        }
645
 
        // 3 attempts is enough to push the ErrExcessiveContention case out of the
646
 
        // realm of plausibility: it implies local state indicating unprovisioned,
647
 
        // and remote state indicating provisioned (reasonable); but which changes
648
 
        // back to unprovisioned and then to provisioned again with *very* specific
649
 
        // timing in the course of this loop.
650
 
        for i := 0; i < 3; i++ {
651
 
                if m.doc.Life != Alive {
652
 
                        return errNotAlive
653
 
                }
654
 
                if _, err := m.InstanceId(); err == nil {
655
 
                        return fmt.Errorf("machine is already provisioned")
656
 
                } else if !IsNotProvisionedError(err) {
657
 
                        return err
658
 
                }
659
 
                if err := m.st.runTransaction(ops); err != txn.ErrAborted {
660
 
                        return err
661
 
                }
662
 
                if m, err = m.st.Machine(m.doc.Id); err != nil {
663
 
                        return err
664
 
                }
665
 
        }
666
 
        return ErrExcessiveContention
667
 
}
668
 
 
669
 
// Status returns the status of the machine.
670
 
func (m *Machine) Status() (status params.Status, info string, err error) {
671
 
        doc, err := getStatus(m.st, m.globalKey())
672
 
        if err != nil {
673
 
                return "", "", err
674
 
        }
675
 
        status = doc.Status
676
 
        info = doc.StatusInfo
677
 
        return
678
 
}
679
 
 
680
 
// SetStatus sets the status of the machine.
681
 
func (m *Machine) SetStatus(status params.Status, info string) error {
682
 
        if status == params.StatusError && info == "" {
683
 
                panic("machine error status with no info")
684
 
        }
685
 
        if status == params.StatusPending {
686
 
                panic("machine status cannot be set to pending")
687
 
        }
688
 
        doc := statusDoc{
689
 
                Status:     status,
690
 
                StatusInfo: info,
691
 
        }
692
 
        ops := []txn.Op{{
693
 
                C:      m.st.machines.Name,
694
 
                Id:     m.doc.Id,
695
 
                Assert: notDeadDoc,
696
 
        },
697
 
                updateStatusOp(m.st, m.globalKey(), doc),
698
 
        }
699
 
        if err := m.st.runTransaction(ops); err != nil {
700
 
                return fmt.Errorf("cannot set status of machine %q: %v", m, onAbort(err, errNotAlive))
701
 
        }
702
 
        return nil
703
 
}
704
 
 
705
 
// Clean returns true if the machine does not have any deployed units or containers.
706
 
func (m *Machine) Clean() bool {
707
 
        return m.doc.Clean
708
 
}