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