~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/state/annotator.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package state
 
2
 
 
3
import (
 
4
        "fmt"
 
5
        "labix.org/v2/mgo"
 
6
        "labix.org/v2/mgo/txn"
 
7
        "launchpad.net/juju-core/utils"
 
8
        "strings"
 
9
)
 
10
 
 
11
// annotatorDoc represents the internal state of annotations for an Entity in
 
12
// MongoDB. Note that the annotations map is not maintained in local storage
 
13
// due to the fact that it is not accessed directly, but through
 
14
// Annotations/Annotation below.
 
15
// Note also the correspondence with AnnotationInfo in state/api/params.
 
16
type annotatorDoc struct {
 
17
        GlobalKey   string `bson:"_id"`
 
18
        Tag         string
 
19
        Annotations map[string]string
 
20
}
 
21
 
 
22
// annotator implements annotation-related methods
 
23
// for any entity that wishes to use it.
 
24
type annotator struct {
 
25
        globalKey string
 
26
        tag       string
 
27
        st        *State
 
28
}
 
29
 
 
30
// SetAnnotations adds key/value pairs to annotations in MongoDB.
 
31
func (a *annotator) SetAnnotations(pairs map[string]string) (err error) {
 
32
        defer utils.ErrorContextf(&err, "cannot update annotations on %s", a.tag)
 
33
        if len(pairs) == 0 {
 
34
                return nil
 
35
        }
 
36
        // Collect in separate maps pairs to be inserted/updated or removed.
 
37
        toRemove := make(map[string]bool)
 
38
        toInsert := make(map[string]string)
 
39
        toUpdate := make(map[string]string)
 
40
        for key, value := range pairs {
 
41
                if strings.Contains(key, ".") {
 
42
                        return fmt.Errorf("invalid key %q", key)
 
43
                }
 
44
                if value == "" {
 
45
                        toRemove["annotations."+key] = true
 
46
                } else {
 
47
                        toInsert[key] = value
 
48
                        toUpdate["annotations."+key] = value
 
49
                }
 
50
        }
 
51
        // Two attempts should be enough to update annotations even with racing
 
52
        // clients - if the document does not already exist, one of the clients
 
53
        // will create it and the others will fail, then all the rest of the
 
54
        // clients should succeed on their second attempt. If the referred-to
 
55
        // entity has disappeared, and removed its annotations in the meantime,
 
56
        // we consider that worthy of an error (will be fixed when new entities
 
57
        // can never share names with old ones).
 
58
        for i := 0; i < 2; i++ {
 
59
                var ops []txn.Op
 
60
                if count, err := a.st.annotations.FindId(a.globalKey).Count(); err != nil {
 
61
                        return err
 
62
                } else if count == 0 {
 
63
                        // Check that the annotator entity was not previously destroyed.
 
64
                        if i != 0 {
 
65
                                return fmt.Errorf("%s no longer exists", a.tag)
 
66
                        }
 
67
                        ops, err = a.insertOps(toInsert)
 
68
                        if err != nil {
 
69
                                return err
 
70
                        }
 
71
                } else {
 
72
                        ops = a.updateOps(toUpdate, toRemove)
 
73
                }
 
74
                if err := a.st.runner.Run(ops, "", nil); err == nil {
 
75
                        return nil
 
76
                } else if err != txn.ErrAborted {
 
77
                        return err
 
78
                }
 
79
        }
 
80
        return ErrExcessiveContention
 
81
}
 
82
 
 
83
// insertOps returns the operations required to insert annotations in MongoDB.
 
84
func (a *annotator) insertOps(toInsert map[string]string) ([]txn.Op, error) {
 
85
        tag := a.tag
 
86
        ops := []txn.Op{{
 
87
                C:      a.st.annotations.Name,
 
88
                Id:     a.globalKey,
 
89
                Assert: txn.DocMissing,
 
90
                Insert: &annotatorDoc{a.globalKey, tag, toInsert},
 
91
        }}
 
92
        if strings.HasPrefix(tag, "environment-") {
 
93
                return ops, nil
 
94
        }
 
95
        // If the entity is not the environment, add a DocExists check on the
 
96
        // entity document, in order to avoid possible races between entity
 
97
        // removal and annotation creation.
 
98
        coll, id, err := a.st.ParseTag(tag)
 
99
        if err != nil {
 
100
                return nil, err
 
101
        }
 
102
        return append(ops, txn.Op{
 
103
                C:      coll,
 
104
                Id:     id,
 
105
                Assert: txn.DocExists,
 
106
        }), nil
 
107
}
 
108
 
 
109
// updateOps returns the operations required to update or remove annotations in MongoDB.
 
110
func (a *annotator) updateOps(toUpdate map[string]string, toRemove map[string]bool) []txn.Op {
 
111
        return []txn.Op{{
 
112
                C:      a.st.annotations.Name,
 
113
                Id:     a.globalKey,
 
114
                Assert: txn.DocExists,
 
115
                Update: D{{"$set", toUpdate}, {"$unset", toRemove}},
 
116
        }}
 
117
}
 
118
 
 
119
// Annotations returns all the annotations corresponding to an entity.
 
120
func (a *annotator) Annotations() (map[string]string, error) {
 
121
        doc := new(annotatorDoc)
 
122
        err := a.st.annotations.FindId(a.globalKey).One(doc)
 
123
        if err == mgo.ErrNotFound {
 
124
                // Returning an empty map if there are no annotations.
 
125
                return make(map[string]string), nil
 
126
        }
 
127
        if err != nil {
 
128
                return nil, err
 
129
        }
 
130
        return doc.Annotations, nil
 
131
}
 
132
 
 
133
// Annotation returns the annotation value corresponding to the given key.
 
134
// If the requested annotation is not found, an empty string is returned.
 
135
func (a *annotator) Annotation(key string) (string, error) {
 
136
        ann, err := a.Annotations()
 
137
        if err != nil {
 
138
                return "", err
 
139
        }
 
140
        return ann[key], nil
 
141
}
 
142
 
 
143
// annotationRemoveOp returns an operation to remove a given annotation
 
144
// document from MongoDB.
 
145
func annotationRemoveOp(st *State, id string) txn.Op {
 
146
        return txn.Op{
 
147
                C:      st.annotations.Name,
 
148
                Id:     id,
 
149
                Remove: true,
 
150
        }
 
151
}