github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/endpoint_bindings.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  	"github.com/juju/errors"
     8  	jujutxn "github.com/juju/txn"
     9  	"github.com/juju/utils/set"
    10  	"gopkg.in/juju/charm.v6-unstable"
    11  	"gopkg.in/mgo.v2"
    12  	"gopkg.in/mgo.v2/bson"
    13  	"gopkg.in/mgo.v2/txn"
    14  )
    15  
    16  // endpointBindingsDoc represents how a service endpoints are bound to spaces.
    17  // The DocID field contains the service's global key, so there is always one
    18  // endpointBindingsDoc per service.
    19  type endpointBindingsDoc struct {
    20  	// DocID is always the same as a service's global key.
    21  	DocID string `bson:"_id"`
    22  
    23  	// Bindings maps a service endpoint name to the space name it is bound to.
    24  	Bindings bindingsMap `bson:"bindings"`
    25  
    26  	// TxnRevno is used to assert the collection have not changed since this
    27  	// document was fetched.
    28  	TxnRevno int64 `bson:"txn-revno"`
    29  }
    30  
    31  // bindingsMap is the underlying type stored in mongo for bindings.
    32  type bindingsMap map[string]string
    33  
    34  // SetBSON ensures any special characters ($ or .) are unescaped in keys after
    35  // unmarshalling the raw BSON coming from the stored document.
    36  func (bp *bindingsMap) SetBSON(raw bson.Raw) error {
    37  	rawMap := make(map[string]string)
    38  	if err := raw.Unmarshal(rawMap); err != nil {
    39  		return err
    40  	}
    41  	for key, value := range rawMap {
    42  		newKey := unescapeReplacer.Replace(key)
    43  		if newKey != key {
    44  			delete(rawMap, key)
    45  		}
    46  		rawMap[newKey] = value
    47  	}
    48  	*bp = bindingsMap(rawMap)
    49  	return nil
    50  }
    51  
    52  // GetBSON ensures any special characters ($ or .) are escaped in keys before
    53  // marshalling the map into BSON and storing in mongo.
    54  func (b bindingsMap) GetBSON() (interface{}, error) {
    55  	if b == nil || len(b) == 0 {
    56  		// We need to return a non-nil map otherwise bson.Unmarshal
    57  		// call will fail when reading the doc back.
    58  		return make(map[string]string), nil
    59  	}
    60  	rawMap := make(map[string]string, len(b))
    61  	for key, value := range b {
    62  		newKey := escapeReplacer.Replace(key)
    63  		rawMap[newKey] = value
    64  	}
    65  	return rawMap, nil
    66  }
    67  
    68  // mergeBindings returns the effective bindings, by combining the default
    69  // bindings based on the given charm metadata, overriding them first with
    70  // matching oldMap values, and then with newMap values (for the same keys).
    71  // newMap and oldMap are both optional and will ignored when empty. Returns a
    72  // map containing only those bindings that need updating, and a sorted slice of
    73  // keys to remove (if any) - those are present in oldMap but missing in both
    74  // newMap and defaults.
    75  func mergeBindings(newMap, oldMap map[string]string, meta *charm.Meta) (map[string]string, []string, error) {
    76  	defaultsMap := DefaultEndpointBindingsForCharm(meta)
    77  
    78  	// defaultsMap contains all endpoints that must be bound for the given charm
    79  	// metadata, but we need to figure out which value to use for each key.
    80  	updated := make(map[string]string)
    81  	for key, defaultValue := range defaultsMap {
    82  		effectiveValue := defaultValue
    83  
    84  		oldValue, hasOld := oldMap[key]
    85  		if hasOld && oldValue != effectiveValue {
    86  			effectiveValue = oldValue
    87  		}
    88  
    89  		newValue, hasNew := newMap[key]
    90  		if hasNew && newValue != effectiveValue {
    91  			effectiveValue = newValue
    92  		}
    93  
    94  		updated[key] = effectiveValue
    95  	}
    96  
    97  	// Any other bindings in newMap are most likely extraneous, but add them
    98  	// anyway and let the validation handle them.
    99  	for key, newValue := range newMap {
   100  		if _, defaultExists := defaultsMap[key]; !defaultExists {
   101  			updated[key] = newValue
   102  		}
   103  	}
   104  
   105  	// All defaults were processed, so anything else in oldMap not about to be
   106  	// updated and not having a default for the given metadata needs to be
   107  	// removed.
   108  	removedKeys := set.NewStrings()
   109  	for key := range oldMap {
   110  		if _, updating := updated[key]; !updating {
   111  			removedKeys.Add(key)
   112  		}
   113  		if _, defaultExists := defaultsMap[key]; !defaultExists {
   114  			removedKeys.Add(key)
   115  		}
   116  	}
   117  	removed := removedKeys.SortedValues()
   118  	return updated, removed, nil
   119  }
   120  
   121  // createEndpointBindingsOp returns the op needed to create new endpoint
   122  // bindings using the optional givenMap and the specified charm metadata to for
   123  // determining defaults and to validate the effective bindings.
   124  func createEndpointBindingsOp(st *State, key string, givenMap map[string]string, meta *charm.Meta) (txn.Op, error) {
   125  
   126  	// No existing map to merge, just use the defaults.
   127  	initialMap, _, err := mergeBindings(givenMap, nil, meta)
   128  	if err != nil {
   129  		return txn.Op{}, errors.Trace(err)
   130  	}
   131  
   132  	// Validate the bindings before inserting.
   133  	if err := validateEndpointBindingsForCharm(st, initialMap, meta); err != nil {
   134  		return txn.Op{}, errors.Trace(err)
   135  	}
   136  
   137  	return txn.Op{
   138  		C:      endpointBindingsC,
   139  		Id:     key,
   140  		Assert: txn.DocMissing,
   141  		Insert: endpointBindingsDoc{
   142  			Bindings: initialMap,
   143  		},
   144  	}, nil
   145  }
   146  
   147  // updateEndpointBindingsOp returns an op that merges the existing bindings with
   148  // givenMap, using newMeta to validate the merged bindings, and asserting the
   149  // existing ones haven't changed in the since we fetched them.
   150  func updateEndpointBindingsOp(st *State, key string, givenMap map[string]string, newMeta *charm.Meta) (txn.Op, error) {
   151  	// Fetch existing bindings.
   152  	existingMap, txnRevno, err := readEndpointBindings(st, key)
   153  	if err != nil && !errors.IsNotFound(err) {
   154  		return txn.Op{}, errors.Trace(err)
   155  	}
   156  
   157  	// Merge existing with given as needed.
   158  	updatedMap, removedKeys, err := mergeBindings(givenMap, existingMap, newMeta)
   159  	if err != nil {
   160  		return txn.Op{}, errors.Trace(err)
   161  	}
   162  
   163  	// Validate the bindings before updating.
   164  	if err := validateEndpointBindingsForCharm(st, updatedMap, newMeta); err != nil {
   165  		return txn.Op{}, errors.Trace(err)
   166  	}
   167  
   168  	// Prepare the update operations.
   169  	sanitize := inSubdocEscapeReplacer("bindings")
   170  	changes := make(bson.M, len(updatedMap))
   171  	for endpoint, space := range updatedMap {
   172  		changes[sanitize(endpoint)] = space
   173  	}
   174  	deletes := make(bson.M, len(removedKeys))
   175  	for _, endpoint := range removedKeys {
   176  		deletes[sanitize(endpoint)] = 1
   177  	}
   178  
   179  	var update bson.D
   180  	if len(changes) != 0 {
   181  		update = append(update, bson.DocElem{Name: "$set", Value: changes})
   182  	}
   183  	if len(deletes) != 0 {
   184  		update = append(update, bson.DocElem{Name: "$unset", Value: deletes})
   185  	}
   186  	if len(update) == 0 {
   187  		return txn.Op{}, jujutxn.ErrNoOperations
   188  	}
   189  	updateOp := txn.Op{
   190  		C:      endpointBindingsC,
   191  		Id:     key,
   192  		Update: update,
   193  	}
   194  	if existingMap != nil {
   195  		// Only assert existing haven't changed when they actually exist.
   196  		updateOp.Assert = bson.D{{"txn-revno", txnRevno}}
   197  	}
   198  	return updateOp, nil
   199  }
   200  
   201  // removeEndpointBindingsOp returns an op removing the bindings for the given
   202  // key, without asserting they exist in the first place.
   203  func removeEndpointBindingsOp(key string) txn.Op {
   204  	return txn.Op{
   205  		C:      endpointBindingsC,
   206  		Id:     key,
   207  		Remove: true,
   208  	}
   209  }
   210  
   211  // readEndpointBindings returns the stored bindings and TxnRevno for the given
   212  // service global key, or an error satisfying errors.IsNotFound() otherwise.
   213  func readEndpointBindings(st *State, key string) (map[string]string, int64, error) {
   214  	endpointBindings, closer := st.getCollection(endpointBindingsC)
   215  	defer closer()
   216  
   217  	var doc endpointBindingsDoc
   218  	err := endpointBindings.FindId(key).One(&doc)
   219  	if err == mgo.ErrNotFound {
   220  		return nil, 0, errors.NotFoundf("endpoint bindings for %q", key)
   221  	}
   222  	if err != nil {
   223  		return nil, 0, errors.Annotatef(err, "cannot get endpoint bindings for %q", key)
   224  	}
   225  
   226  	return doc.Bindings, doc.TxnRevno, nil
   227  }
   228  
   229  // validateEndpointBindingsForCharm verifies that all endpoint names in bindings
   230  // are valid for the given charm metadata, and each endpoint is bound to a known
   231  // space - otherwise an error satisfying errors.IsNotValid() will be returned.
   232  func validateEndpointBindingsForCharm(st *State, bindings map[string]string, charmMeta *charm.Meta) error {
   233  	if st == nil {
   234  		return errors.NotValidf("nil state")
   235  	}
   236  	if bindings == nil {
   237  		return errors.NotValidf("nil bindings")
   238  	}
   239  	if charmMeta == nil {
   240  		return errors.NotValidf("nil charm metadata")
   241  	}
   242  	spaces, err := st.AllSpaces()
   243  	if err != nil {
   244  		return errors.Trace(err)
   245  	}
   246  
   247  	spacesNamesSet := set.NewStrings()
   248  	for _, space := range spaces {
   249  		spacesNamesSet.Add(space.Name())
   250  	}
   251  
   252  	allBindings := DefaultEndpointBindingsForCharm(charmMeta)
   253  	endpointsNamesSet := set.NewStrings()
   254  	for name := range allBindings {
   255  		endpointsNamesSet.Add(name)
   256  	}
   257  
   258  	// Ensure there are no unknown endpoints and/or spaces specified.
   259  	//
   260  	// TODO(dimitern): This assumes spaces cannot be deleted when they are used
   261  	// in bindings. In follow-up, this will be enforced by using refcounts on
   262  	// spaces.
   263  	for endpoint, space := range bindings {
   264  		if !endpointsNamesSet.Contains(endpoint) {
   265  			return errors.NotValidf("unknown endpoint %q", endpoint)
   266  		}
   267  		if space != "" && !spacesNamesSet.Contains(space) {
   268  			return errors.NotValidf("unknown space %q", space)
   269  		}
   270  	}
   271  	return nil
   272  }
   273  
   274  // DefaultEndpointBindingsForCharm populates a bindings map containing each
   275  // endpoint of the given charm metadata (relation name or extra-binding name)
   276  // bound to an empty space.
   277  func DefaultEndpointBindingsForCharm(charmMeta *charm.Meta) map[string]string {
   278  	allRelations := charmMeta.CombinedRelations()
   279  	bindings := make(map[string]string, len(allRelations)+len(charmMeta.ExtraBindings))
   280  	for name := range allRelations {
   281  		bindings[name] = ""
   282  	}
   283  	for name := range charmMeta.ExtraBindings {
   284  		bindings[name] = ""
   285  	}
   286  	return bindings
   287  }