5
6
"labix.org/v2/mgo/txn"
7
"launchpad.net/juju-core/utils"
9
// annotator stores annotations and information required to query MongoDB.
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"`
19
Annotations map[string]string
22
// annotator implements annotation-related methods
23
// for any entity that wishes to use it.
10
24
type annotator struct {
11
annotations *map[string]string
17
// SetAnnotation adds a key/value pair to annotations in MongoDB and the annotator.
18
func (a *annotator) SetAnnotation(key, value string) error {
19
if strings.Contains(key, ".") {
20
return fmt.Errorf("invalid key %q", key)
22
if *a.annotations == nil {
23
*a.annotations = make(map[string]string)
26
// Delete a key/value pair in MongoDB.
31
Update: D{{"$unset", D{{"annotations." + key, true}}}},
33
if err := a.st.runner.Run(ops, "", nil); err != nil {
34
return fmt.Errorf("cannot delete annotation %q: %v", key, onAbort(err, errNotAlive))
36
delete(*a.annotations, key)
38
// Set a key/value pair in MongoDB.
43
Update: D{{"$set", D{{"annotations." + key, value}}}},
45
if err := a.st.runner.Run(ops, "", nil); err != nil {
46
return fmt.Errorf("cannot set annotation %q = %q: %v", key, value, onAbort(err, errNotAlive))
48
(*a.annotations)[key] = value
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)
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)
45
toRemove["annotations."+key] = true
48
toUpdate["annotations."+key] = value
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++ {
60
if count, err := a.st.annotations.FindId(a.globalKey).Count(); err != nil {
62
} else if count == 0 {
63
// Check that the annotator entity was not previously destroyed.
65
return fmt.Errorf("%s no longer exists", a.tag)
67
ops, err = a.insertOps(toInsert)
72
ops = a.updateOps(toUpdate, toRemove)
74
if err := a.st.runner.Run(ops, "", nil); err == nil {
76
} else if err != txn.ErrAborted {
80
return ErrExcessiveContention
83
// insertOps returns the operations required to insert annotations in MongoDB.
84
func (a *annotator) insertOps(toInsert map[string]string) ([]txn.Op, error) {
87
C: a.st.annotations.Name,
89
Assert: txn.DocMissing,
90
Insert: &annotatorDoc{a.globalKey, tag, toInsert},
92
if strings.HasPrefix(tag, "environment-") {
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)
102
return append(ops, txn.Op{
105
Assert: txn.DocExists,
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 {
112
C: a.st.annotations.Name,
114
Assert: txn.DocExists,
115
Update: D{{"$set", toUpdate}, {"$unset", toRemove}},
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
130
return doc.Annotations, nil
53
133
// Annotation returns the annotation value corresponding to the given key.
54
func (a annotator) Annotation(key string) string {
55
return (*a.annotations)[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()
143
// annotationRemoveOp returns an operation to remove a given annotation
144
// document from MongoDB.
145
func annotationRemoveOp(st *State, id string) txn.Op {
147
C: st.annotations.Name,