~themue/juju-core/021-deployer-lxc-context

« back to all changes in this revision

Viewing changes to state/annotator.go

  • Committer: Frank Mueller
  • Date: 2013-05-09 22:35:21 UTC
  • mfrom: (964.1.245 juju-core)
  • Revision ID: themue@gmail.com-20130509223521-12m9j2dtdx65tvr2
lxc: refactored into own package; first network code

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
 
3
3
import (
4
4
        "fmt"
 
5
        "labix.org/v2/mgo"
5
6
        "labix.org/v2/mgo/txn"
 
7
        "launchpad.net/juju-core/utils"
6
8
        "strings"
7
9
)
8
10
 
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"`
 
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.
10
24
type annotator struct {
11
 
        annotations *map[string]string
12
 
        st          *State
13
 
        coll        string
14
 
        id          string
15
 
}
16
 
 
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)
21
 
        }
22
 
        if *a.annotations == nil {
23
 
                *a.annotations = make(map[string]string)
24
 
        }
25
 
        if value == "" {
26
 
                // Delete a key/value pair in MongoDB.
27
 
                ops := []txn.Op{{
28
 
                        C:      a.coll,
29
 
                        Id:     a.id,
30
 
                        Assert: isAliveDoc,
31
 
                        Update: D{{"$unset", D{{"annotations." + key, true}}}},
32
 
                }}
33
 
                if err := a.st.runner.Run(ops, "", nil); err != nil {
34
 
                        return fmt.Errorf("cannot delete annotation %q: %v", key, onAbort(err, errNotAlive))
35
 
                }
36
 
                delete(*a.annotations, key)
37
 
        } else {
38
 
                // Set a key/value pair in MongoDB.
39
 
                ops := []txn.Op{{
40
 
                        C:      a.coll,
41
 
                        Id:     a.id,
42
 
                        Assert: isAliveDoc,
43
 
                        Update: D{{"$set", D{{"annotations." + key, value}}}},
44
 
                }}
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))
47
 
                }
48
 
                (*a.annotations)[key] = value
49
 
        }
50
 
        return nil
 
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
51
131
}
52
132
 
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()
 
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
        }
56
151
}