github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	EnvUUID     string            `bson:"env-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["annotations."+key] = true
    45  		} else {
    46  			toInsert[key] = value
    47  			toUpdate["annotations."+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  	switch tag.(type) {
   113  	case names.EnvironTag:
   114  		return ops, nil
   115  	}
   116  	// If the entity is not the environment, add a DocExists check on the
   117  	// entity document, in order to avoid possible races between entity
   118  	// removal and annotation creation.
   119  	coll, id, err := st.tagToCollectionAndId(tag)
   120  	if err != nil {
   121  		return nil, errors.Trace(err)
   122  	}
   123  	return append(ops, txn.Op{
   124  		C:      coll,
   125  		Id:     id,
   126  		Assert: txn.DocExists,
   127  	}), nil
   128  }
   129  
   130  // updateAnnotations returns the operations required to update or remove annotations in MongoDB.
   131  func updateAnnotations(st *State, entity GlobalEntity, toUpdate, toRemove bson.M) []txn.Op {
   132  	return []txn.Op{{
   133  		C:      annotationsC,
   134  		Id:     st.docID(entity.globalKey()),
   135  		Assert: txn.DocExists,
   136  		Update: setUnsetUpdate(toUpdate, toRemove),
   137  	}}
   138  }
   139  
   140  // annotationRemoveOp returns an operation to remove a given annotation
   141  // document from MongoDB.
   142  func annotationRemoveOp(st *State, id string) txn.Op {
   143  	return txn.Op{
   144  		C:      annotationsC,
   145  		Id:     st.docID(id),
   146  		Remove: true,
   147  	}
   148  }