github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/state/annotations.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/names" 12 "gopkg.in/mgo.v2" 13 "gopkg.in/mgo.v2/bson" 14 "gopkg.in/mgo.v2/txn" 15 ) 16 17 // annotatorDoc represents the internal state of annotations for an Entity in 18 // MongoDB. Note that the annotations map is not maintained in local storage 19 // due to the fact that it is not accessed directly, but through 20 // Annotations/Annotation below. 21 // Note also the correspondence with AnnotationInfo in apiserver/params. 22 type annotatorDoc struct { 23 ModelUUID string `bson:"model-uuid"` 24 GlobalKey string `bson:"globalkey"` 25 Tag string `bson:"tag"` 26 Annotations map[string]string `bson:"annotations"` 27 } 28 29 // SetAnnotations adds key/value pairs to annotations in MongoDB. 30 func (st *State) SetAnnotations(entity GlobalEntity, annotations map[string]string) (err error) { 31 defer errors.DeferredAnnotatef(&err, "cannot update annotations on %s", entity.Tag()) 32 if len(annotations) == 0 { 33 return nil 34 } 35 // Collect in separate maps pairs to be inserted/updated or removed. 36 toRemove := make(bson.M) 37 toInsert := make(map[string]string) 38 toUpdate := make(bson.M) 39 for key, value := range annotations { 40 if strings.Contains(key, ".") { 41 return fmt.Errorf("invalid key %q", key) 42 } 43 if value == "" { 44 toRemove[key] = true 45 } else { 46 toInsert[key] = value 47 toUpdate[key] = value 48 } 49 } 50 // Set up and call the necessary transactions - if the document does not 51 // already exist, one of the clients will create it and the others will 52 // fail, then all the rest of the clients should succeed on their second 53 // attempt. If the referred-to entity has disappeared, and removed its 54 // annotations in the meantime, we consider that worthy of an error 55 // (will be fixed when new entities can never share names with old ones). 56 buildTxn := func(attempt int) ([]txn.Op, error) { 57 annotations, closer := st.getCollection(annotationsC) 58 defer closer() 59 if count, err := annotations.FindId(entity.globalKey()).Count(); err != nil { 60 return nil, err 61 } else if count == 0 { 62 // Check that the annotator entity was not previously destroyed. 63 if attempt != 0 { 64 return nil, fmt.Errorf("%s no longer exists", entity.Tag()) 65 } 66 return insertAnnotationsOps(st, entity, toInsert) 67 } 68 return updateAnnotations(st, entity, toUpdate, toRemove), nil 69 } 70 return st.run(buildTxn) 71 } 72 73 // Annotations returns all the annotations corresponding to an entity. 74 func (st *State) Annotations(entity GlobalEntity) (map[string]string, error) { 75 doc := new(annotatorDoc) 76 annotations, closer := st.getCollection(annotationsC) 77 defer closer() 78 err := annotations.FindId(entity.globalKey()).One(doc) 79 if err == mgo.ErrNotFound { 80 // Returning an empty map if there are no annotations. 81 return make(map[string]string), nil 82 } 83 if err != nil { 84 return nil, errors.Trace(err) 85 } 86 return doc.Annotations, nil 87 } 88 89 // Annotation returns the annotation value corresponding to the given key. 90 // If the requested annotation is not found, an empty string is returned. 91 func (st *State) Annotation(entity GlobalEntity, key string) (string, error) { 92 ann, err := st.Annotations(entity) 93 if err != nil { 94 return "", errors.Trace(err) 95 } 96 return ann[key], nil 97 } 98 99 // insertAnnotationsOps returns the operations required to insert annotations in MongoDB. 100 func insertAnnotationsOps(st *State, entity GlobalEntity, toInsert map[string]string) ([]txn.Op, error) { 101 tag := entity.Tag() 102 ops := []txn.Op{{ 103 C: annotationsC, 104 Id: st.docID(entity.globalKey()), 105 Assert: txn.DocMissing, 106 Insert: &annotatorDoc{ 107 GlobalKey: entity.globalKey(), 108 Tag: tag.String(), 109 Annotations: toInsert, 110 }, 111 }} 112 113 switch tag := tag.(type) { 114 case names.ModelTag: 115 env, err := st.GetModel(tag) 116 if err != nil { 117 return nil, errors.Annotatef(err, "inserting annotations") 118 } 119 if env.UUID() == env.ControllerUUID() { 120 // This is the controller model, and cannot be removed. 121 // Ergo, we can skip the existence check below. 122 return ops, nil 123 } 124 } 125 // If the entity is not the controller model, add a DocExists check on the 126 // entity document, in order to avoid possible races between entity 127 // removal and annotation creation. 128 coll, id, err := st.tagToCollectionAndId(tag) 129 if err != nil { 130 return nil, errors.Trace(err) 131 } 132 return append(ops, txn.Op{ 133 C: coll, 134 Id: id, 135 Assert: txn.DocExists, 136 }), nil 137 } 138 139 // updateAnnotations returns the operations required to update or remove annotations in MongoDB. 140 func updateAnnotations(st *State, entity GlobalEntity, toUpdate, toRemove bson.M) []txn.Op { 141 return []txn.Op{{ 142 C: annotationsC, 143 Id: st.docID(entity.globalKey()), 144 Assert: txn.DocExists, 145 Update: setUnsetUpdateAnnotations(toUpdate, toRemove), 146 }} 147 } 148 149 // annotationRemoveOp returns an operation to remove a given annotation 150 // document from MongoDB. 151 func annotationRemoveOp(st *State, id string) txn.Op { 152 return txn.Op{ 153 C: annotationsC, 154 Id: st.docID(id), 155 Remove: true, 156 } 157 } 158 159 // setUnsetUpdateAnnotations returns a bson.D for use 160 // in an annotationsC txn.Op's Update field, containing $set and 161 // $unset operators if the corresponding operands 162 // are non-empty. 163 func setUnsetUpdateAnnotations(set, unset bson.M) bson.D { 164 var update bson.D 165 replace := inSubdocReplacer("annotations") 166 if len(set) > 0 { 167 set = bson.M(copyMap(map[string]interface{}(set), replace)) 168 update = append(update, bson.DocElem{"$set", set}) 169 } 170 if len(unset) > 0 { 171 unset = bson.M(copyMap(map[string]interface{}(unset), replace)) 172 update = append(update, bson.DocElem{"$unset", unset}) 173 } 174 return update 175 }