github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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  	"fmt"
     8  
     9  	"github.com/juju/charm/v12"
    10  	"github.com/juju/collections/set"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/mgo/v3"
    13  	"github.com/juju/mgo/v3/bson"
    14  	"github.com/juju/mgo/v3/txn"
    15  	jujutxn "github.com/juju/txn/v3"
    16  
    17  	"github.com/juju/juju/core/network"
    18  	"github.com/juju/juju/mongo/utils"
    19  )
    20  
    21  // defaultEndpointName is the key in the bindings map that stores the
    22  // space name that endpoints should be bound to if they aren't found
    23  // individually.
    24  const defaultEndpointName = ""
    25  
    26  // endpointBindingsDoc represents how a application's endpoints are bound to spaces.
    27  // The DocID field contains the applications's global key, so there is always one
    28  // endpointBindingsDoc per application.
    29  type endpointBindingsDoc struct {
    30  	// DocID is always the same as a application's global key.
    31  	DocID string `bson:"_id"`
    32  
    33  	// Bindings maps an application endpoint name to the space ID it is bound to.
    34  	Bindings bindingsMap `bson:"bindings"`
    35  
    36  	// TxnRevno is used to assert the collection have not changed since this
    37  	// document was fetched.
    38  	TxnRevno int64 `bson:"txn-revno"`
    39  }
    40  
    41  // bindingsMap is the underlying type stored in mongo for bindings.
    42  type bindingsMap map[string]string
    43  
    44  // SetBSON ensures any special characters ($ or .) are unescaped in keys after
    45  // unmarshalling the raw BSON coming from the stored document.
    46  func (b *bindingsMap) SetBSON(raw bson.Raw) error {
    47  	rawMap := make(map[string]string)
    48  	if err := raw.Unmarshal(rawMap); err != nil {
    49  		return err
    50  	}
    51  	for key, value := range rawMap {
    52  		newKey := utils.UnescapeKey(key)
    53  		if newKey != key {
    54  			delete(rawMap, key)
    55  		}
    56  		rawMap[newKey] = value
    57  	}
    58  	*b = bindingsMap(rawMap)
    59  	return nil
    60  }
    61  
    62  // GetBSON ensures any special characters ($ or .) are escaped in keys before
    63  // marshalling the map into BSON and storing in mongo.
    64  func (b bindingsMap) GetBSON() (interface{}, error) {
    65  	if b == nil || len(b) == 0 {
    66  		// We need to return a non-nil map otherwise bson.Unmarshal
    67  		// call will fail when reading the doc back.
    68  		return make(map[string]string), nil
    69  	}
    70  	rawMap := make(map[string]string, len(b))
    71  	for key, value := range b {
    72  		newKey := utils.EscapeKey(key)
    73  		rawMap[newKey] = value
    74  	}
    75  
    76  	return rawMap, nil
    77  }
    78  
    79  // Merge the default bindings based on the given charm metadata with the
    80  // current bindings, overriding with mergeWith values (for the same keys).
    81  // Current values and mergeWith are both optional and will ignored when
    82  // empty. The current object contains the combined finalized bindings.
    83  // Returns true/false if there are any actual differences.
    84  func (b *Bindings) Merge(mergeWith map[string]string, meta *charm.Meta) (bool, error) {
    85  	// Verify the bindings to be merged, and ensure we're merging with
    86  	// space ids.
    87  	merge, err := NewBindings(b.st, mergeWith)
    88  	if err != nil {
    89  		return false, errors.Trace(err)
    90  	}
    91  	mergeMap := merge.Map()
    92  
    93  	defaultsMap, err := DefaultEndpointBindingsForCharm(b.st, meta)
    94  	if err != nil {
    95  		return false, errors.Trace(err)
    96  	}
    97  
    98  	defaultBinding, mergeOK := b.bindingsMap[defaultEndpointName]
    99  	if !mergeOK {
   100  		var err error
   101  		defaultBinding, err = b.st.DefaultEndpointBindingSpace()
   102  		if err != nil {
   103  			return false, errors.Trace(err)
   104  		}
   105  	}
   106  	if newDefaultBinding, newOk := mergeMap[defaultEndpointName]; newOk {
   107  		// new default binding supersedes the old default binding
   108  		defaultBinding = newDefaultBinding
   109  	}
   110  
   111  	// defaultsMap contains all endpoints that must be bound for the given charm
   112  	// metadata, but we need to figure out which value to use for each key.
   113  	updated := make(map[string]string)
   114  	updated[defaultEndpointName] = defaultBinding
   115  	for key, defaultValue := range defaultsMap {
   116  		effectiveValue := defaultValue
   117  
   118  		currentValue, hasCurrent := b.bindingsMap[key]
   119  		if hasCurrent {
   120  			if currentValue != effectiveValue {
   121  				effectiveValue = currentValue
   122  			}
   123  		} else {
   124  			// current didn't talk about this value, but maybe we have a default
   125  			effectiveValue = defaultBinding
   126  		}
   127  
   128  		mergeValue, hasMerge := mergeMap[key]
   129  		if hasMerge && mergeValue != effectiveValue && mergeValue != "" {
   130  			effectiveValue = mergeValue
   131  		}
   132  
   133  		updated[key] = effectiveValue
   134  	}
   135  
   136  	// Any other bindings in mergeWith Map are most likely extraneous, but add them
   137  	// anyway and let the validation handle them.
   138  	for key, newValue := range mergeMap {
   139  		if _, defaultExists := defaultsMap[key]; !defaultExists {
   140  			updated[key] = newValue
   141  		}
   142  	}
   143  	isModified := false
   144  	if len(updated) != len(b.bindingsMap) {
   145  		isModified = true
   146  	} else {
   147  		// If the len() is identical, then we know as long as we iterate all entries, then there is no way to
   148  		// miss an entry. Either they have identical keys and we check all the values, or there is an identical
   149  		// number of new keys and missing keys and we'll notice a missing key.
   150  		for key, val := range updated {
   151  			if oldVal, existed := b.bindingsMap[key]; !existed || oldVal != val {
   152  				isModified = true
   153  				break
   154  			}
   155  		}
   156  	}
   157  	logger.Debugf("merged endpoint bindings modified: %t, default: %v, current: %v, mergeWith: %v, after: %v",
   158  		isModified, defaultsMap, b.bindingsMap, mergeMap, updated)
   159  	if isModified {
   160  		b.bindingsMap = updated
   161  	}
   162  	return isModified, nil
   163  }
   164  
   165  // createOp returns the op needed to create new endpoint bindings using the
   166  // optional current bindings and the specified charm metadata to for
   167  // determining defaults and to validate the effective bindings.
   168  func (b *Bindings) createOp(bindings map[string]string, meta *charm.Meta) (txn.Op, error) {
   169  	if b.app == nil {
   170  		return txn.Op{}, errors.Trace(errors.New("programming error: app is a nil pointer"))
   171  	}
   172  	// No existing map to Merge, just use the defaults.
   173  	_, err := b.Merge(bindings, meta)
   174  	if err != nil {
   175  		return txn.Op{}, errors.Trace(err)
   176  	}
   177  
   178  	// Validate the bindings before inserting.
   179  	if err := b.validateForCharm(meta); err != nil {
   180  		return txn.Op{}, errors.Trace(err)
   181  	}
   182  
   183  	return txn.Op{
   184  		C:      endpointBindingsC,
   185  		Id:     b.app.globalKey(),
   186  		Assert: txn.DocMissing,
   187  		Insert: endpointBindingsDoc{
   188  			Bindings: b.Map(),
   189  		},
   190  	}, nil
   191  }
   192  
   193  // updateOps returns an op list to update the endpoint bindings for an application.
   194  // The implementation calculates the final set of bindings by merging the provided
   195  // newMap into the existing set of bindings. The final bindings are validated
   196  // in two ways:
   197  //  1. we make sure that the endpoint names in the binding map are all present
   198  //     in the provided charm metadata and that the space IDs actually exist.
   199  //  2. we check that all existing units for the application are executing on
   200  //     machines that have an address in each space we are binding to. This
   201  //     check can be circumvented by setting the force argument to true.
   202  //
   203  // The returned operation list includes additional operations that perform
   204  // the following assertions:
   205  //   - assert that the unit count has not changed while the txn is in progress.
   206  //   - assert that the spaces we are binding to have not been deleted.
   207  //   - assert that the existing bindings we used for calculating the merged set
   208  //     of bindings have not changed while the txn is in progress.
   209  func (b *Bindings) updateOps(txnRevno int64, newMap map[string]string, newMeta *charm.Meta, force bool) ([]txn.Op, error) {
   210  	var ops []txn.Op
   211  
   212  	if b.app == nil {
   213  		return ops, errors.Trace(errors.New("programming error: app is a nil pointer"))
   214  	}
   215  
   216  	useTxnRevno := len(b.bindingsMap) > 0
   217  
   218  	// Merge existing with new as needed.
   219  	isModified, err := b.Merge(newMap, newMeta)
   220  	if err != nil {
   221  		return ops, errors.Trace(err)
   222  	}
   223  
   224  	if !isModified {
   225  		return ops, jujutxn.ErrNoOperations
   226  	}
   227  
   228  	// Validate the bindings before updating.
   229  	if err := b.validateForCharm(newMeta); err != nil {
   230  		return ops, errors.Trace(err)
   231  	}
   232  
   233  	// Make sure that all machines which run units of this application
   234  	// contain addresses in the spaces we are trying to bind to.
   235  	if !force {
   236  		if err := b.validateForMachines(); err != nil {
   237  			return ops, errors.Trace(err)
   238  		}
   239  	}
   240  
   241  	// Ensure that the spaceIDs needed for the bindings exist.
   242  	spIdMap := set.NewStrings()
   243  	for _, spID := range b.Map() {
   244  		sp, err := b.st.Space(spID)
   245  		if err != nil {
   246  			return ops, errors.Trace(err)
   247  		}
   248  		if spIdMap.Contains(spID) {
   249  			continue
   250  		}
   251  		ops = append(ops, txn.Op{
   252  			C:      spacesC,
   253  			Id:     sp.doc.DocId,
   254  			Assert: txn.DocExists,
   255  		})
   256  		spIdMap.Add(spID)
   257  	}
   258  
   259  	// To avoid a potential race where units may suddenly appear on a new
   260  	// machine that does not have addresses for all the required spaces
   261  	// while we are applying the txn, we define an assertion on the unit
   262  	// count for the current application.
   263  	ops = append(ops, txn.Op{
   264  		C:      applicationsC,
   265  		Id:     b.app.doc.DocID,
   266  		Assert: bson.D{{"unitcount", b.app.UnitCount()}},
   267  	})
   268  
   269  	// Prepare the update operations.
   270  	escaped := make(bson.M, len(b.Map()))
   271  	for endpoint, space := range b.Map() {
   272  		escaped[utils.EscapeKey(endpoint)] = space
   273  	}
   274  
   275  	_, bindingsErr := readEndpointBindingsDoc(b.app.st, b.app.globalKey())
   276  	if bindingsErr != nil && !errors.IsNotFound(err) {
   277  		return nil, errors.Trace(err)
   278  	}
   279  	if err != nil {
   280  		// No bindings to update.
   281  		return ops, nil
   282  	}
   283  	updateOp := txn.Op{
   284  		C:      endpointBindingsC,
   285  		Id:     b.app.globalKey(),
   286  		Assert: txn.DocExists,
   287  		Update: bson.M{"$set": bson.M{"bindings": escaped}},
   288  	}
   289  	if useTxnRevno {
   290  		// Only assert existing haven't changed when they actually exist.
   291  		updateOp.Assert = bson.D{{"txn-revno", txnRevno}}
   292  	}
   293  
   294  	return append(ops, updateOp), nil
   295  }
   296  
   297  // validateForMachines ensures that the current set of endpoint to space ID
   298  // bindings (including the default space ID for the app) are feasible given the
   299  // the network configuration settings of the machines where application units
   300  // are already running.
   301  func (b *Bindings) validateForMachines() error {
   302  	if b.app == nil {
   303  		return errors.Trace(errors.New("programming error: app is a nil pointer"))
   304  	}
   305  	// Get a list of deployed machines and create a map where we track the
   306  	// count of deployed machines for each space.
   307  	machineCountInSpace := make(map[string]int)
   308  	deployedMachines, err := b.app.DeployedMachines()
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	for _, m := range deployedMachines {
   314  		machineSpaces, err := m.AllSpaces()
   315  		if err != nil {
   316  			return errors.Annotatef(err, "unable to get space assignments for machine %q", m.Id())
   317  		}
   318  		for spID := range machineSpaces {
   319  			machineCountInSpace[spID]++
   320  		}
   321  	}
   322  
   323  	// We only need to validate changes to the default space ID for the
   324  	// application if the operator is trying to change it to something
   325  	// other than network.DefaultSpaceID
   326  	if newDefaultSpaceIDForApp, defined := b.bindingsMap[defaultEndpointName]; defined && newDefaultSpaceIDForApp != network.AlphaSpaceId {
   327  		if machineCountInSpace[newDefaultSpaceIDForApp] != len(deployedMachines) {
   328  			msg := "changing default space to %q is not feasible: one or more deployed machines lack an address in this space"
   329  			return b.spaceNotFeasibleError(msg, newDefaultSpaceIDForApp)
   330  		}
   331  	}
   332  
   333  	for epName, spID := range b.bindingsMap {
   334  		if epName == "" {
   335  			continue
   336  		}
   337  		// TODO(achilleasa): this check is a temporary workaround
   338  		// to allow upgrading charms that define new endpoints
   339  		// which we automatically bind to the default space if
   340  		// the operator does not explicitly try to bind them
   341  		// to a space.
   342  		//
   343  		// If we deploy a charm with a "spaces=xxx" constraint,
   344  		// it will not have a provider address in the default
   345  		// space so the machine-count check below would
   346  		// otherwise fail.
   347  		if spID == network.AlphaSpaceId {
   348  			continue
   349  		}
   350  
   351  		// Ensure that all currently deployed machines have an address
   352  		// in the requested space for this binding
   353  		if machineCountInSpace[spID] != len(deployedMachines) {
   354  			msg := fmt.Sprintf("binding endpoint %q to ", epName)
   355  			return b.spaceNotFeasibleError(msg+"space %q is not feasible: one or more deployed machines lack an address in this space", spID)
   356  		}
   357  	}
   358  
   359  	return nil
   360  }
   361  
   362  func (b *Bindings) spaceNotFeasibleError(msg, id string) error {
   363  	space, err := b.st.Space(id)
   364  	if err != nil {
   365  		logger.Errorf(msg, id)
   366  		return errors.Annotatef(err, "cannot get space name for id %q", id)
   367  	}
   368  	return errors.Errorf(msg, space.Name())
   369  }
   370  
   371  // removeEndpointBindingsOp returns an op removing the bindings for the given
   372  // key, without asserting they exist in the first place.
   373  func removeEndpointBindingsOp(key string) txn.Op {
   374  	return txn.Op{
   375  		C:      endpointBindingsC,
   376  		Id:     key,
   377  		Remove: true,
   378  	}
   379  }
   380  
   381  // readEndpointBindings returns the stored bindings and TxnRevno for the given
   382  // application global key, or an error satisfying errors.IsNotFound() otherwise.
   383  func readEndpointBindings(st *State, key string) (map[string]string, int64, error) {
   384  	doc, err := readEndpointBindingsDoc(st, key)
   385  	if err != nil {
   386  		return nil, 0, err
   387  	}
   388  	return doc.Bindings, doc.TxnRevno, nil
   389  }
   390  
   391  // readEndpointBindingsDoc returns the endpoint bindings document for the
   392  // specified key.
   393  func readEndpointBindingsDoc(st *State, key string) (*endpointBindingsDoc, error) {
   394  	endpointBindings, closer := st.db().GetCollection(endpointBindingsC)
   395  	defer closer()
   396  
   397  	var doc endpointBindingsDoc
   398  	err := endpointBindings.FindId(key).One(&doc)
   399  	if err == mgo.ErrNotFound {
   400  		return nil, errors.NotFoundf("endpoint bindings for %q", key)
   401  	}
   402  	if err != nil {
   403  		return nil, errors.Annotatef(err, "cannot get endpoint bindings for %q", key)
   404  	}
   405  
   406  	return &doc, nil
   407  }
   408  
   409  // validateForCharm verifies that all endpoint names in bindings
   410  // are valid for the given charm metadata, and each endpoint is bound to a known
   411  // space - otherwise an error satisfying errors.IsNotValid() will be returned.
   412  func (b *Bindings) validateForCharm(charmMeta *charm.Meta) error {
   413  	if b.bindingsMap == nil {
   414  		return errors.NotValidf("nil bindings")
   415  	}
   416  	if charmMeta == nil {
   417  		return errors.NotValidf("nil charm metadata")
   418  	}
   419  
   420  	spaceInfos, err := b.st.AllSpaceInfos()
   421  	if err != nil {
   422  		return errors.Trace(err)
   423  	}
   424  
   425  	allBindings, err := DefaultEndpointBindingsForCharm(b.st, charmMeta)
   426  	if err != nil {
   427  		return errors.Trace(err)
   428  	}
   429  	endpointsNamesSet := set.NewStrings()
   430  	for name := range allBindings {
   431  		endpointsNamesSet.Add(name)
   432  	}
   433  
   434  	// Ensure there are no unknown endpoints and/or spaces specified.
   435  	//
   436  	// TODO(dimitern): This assumes spaces cannot be deleted when they are used
   437  	// in bindings. In follow-up, this will be enforced by using refcounts on
   438  	// spaces.
   439  	for endpoint, space := range b.bindingsMap {
   440  		if endpoint != defaultEndpointName && !endpointsNamesSet.Contains(endpoint) {
   441  			return errors.NotValidf("unknown endpoint %q", endpoint)
   442  		}
   443  		if !spaceInfos.ContainsID(space) {
   444  			return errors.NotValidf("unknown space %q", space)
   445  		}
   446  	}
   447  	return nil
   448  }
   449  
   450  // DefaultEndpointBindingSpace returns the current space ID to be used for
   451  // the default endpoint binding.
   452  func (st *State) DefaultEndpointBindingSpace() (string, error) {
   453  	model, err := st.Model()
   454  	if err != nil {
   455  		return "", errors.Trace(err)
   456  	}
   457  
   458  	cfg, err := model.Config()
   459  	if err != nil {
   460  		return "", errors.Trace(err)
   461  	}
   462  
   463  	defaultBinding := network.AlphaSpaceId
   464  
   465  	space, err := st.SpaceByName(cfg.DefaultSpace())
   466  	if err != nil && !errors.IsNotFound(err) {
   467  		return "", errors.Trace(err)
   468  	}
   469  	if err == nil {
   470  		defaultBinding = space.Id()
   471  	}
   472  
   473  	return defaultBinding, nil
   474  }
   475  
   476  // DefaultEndpointBindingsForCharm populates a bindings map containing each
   477  // endpoint of the given charm metadata (relation name or extra-binding name)
   478  // bound to an empty space.
   479  func DefaultEndpointBindingsForCharm(st EndpointBinding, charmMeta *charm.Meta) (map[string]string, error) {
   480  	defaultBindingSpaceID, err := st.DefaultEndpointBindingSpace()
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  	allRelations := charmMeta.CombinedRelations()
   485  	bindings := make(map[string]string, len(allRelations)+len(charmMeta.ExtraBindings))
   486  	for name := range allRelations {
   487  		bindings[name] = defaultBindingSpaceID
   488  	}
   489  	for name := range charmMeta.ExtraBindings {
   490  		bindings[name] = defaultBindingSpaceID
   491  	}
   492  	return bindings, nil
   493  }
   494  
   495  // EndpointBinding are the methods necessary for exported methods of
   496  // Bindings to work.
   497  //
   498  //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/endpointbinding_mock.go github.com/juju/juju/state EndpointBinding
   499  type EndpointBinding interface {
   500  	network.SpaceLookup
   501  	DefaultEndpointBindingSpace() (string, error)
   502  	Space(id string) (*Space, error)
   503  }
   504  
   505  // Bindings are EndpointBindings.
   506  type Bindings struct {
   507  	st  EndpointBinding
   508  	app *Application
   509  	bindingsMap
   510  }
   511  
   512  // NewBindings returns a bindings guaranteed to be in space id format.
   513  func NewBindings(st EndpointBinding, givenMap map[string]string) (*Bindings, error) {
   514  	// namesErr and idError are only problems if both are not nil.
   515  	spaceInfos, err := st.AllSpaceInfos()
   516  	if err != nil {
   517  		return nil, errors.Trace(err)
   518  	}
   519  
   520  	// If givenMap contains space names empty values are allowed (e.g. they
   521  	// may be present when migrating a model from a 2.6.x controller).
   522  	namesErr := allOfOne(spaceInfos.ContainsName, givenMap, true)
   523  
   524  	// If givenMap contains empty values then the map most probably contains
   525  	// space names. Therefore, we want allOfOne to be strict and bail out
   526  	// if it sees any empty values.
   527  	idErr := allOfOne(spaceInfos.ContainsID, givenMap, false)
   528  
   529  	// Ensure the spaces values are all names OR ids in the
   530  	// given map.
   531  	var newMap map[string]string
   532  	switch {
   533  	case namesErr == nil && idErr == nil:
   534  		// The givenMap is empty or has empty endpoints
   535  		if len(givenMap) > 0 {
   536  			newMap = givenMap
   537  			break
   538  		}
   539  		newMap = make(map[string]string, len(givenMap))
   540  
   541  	case namesErr == nil && idErr != nil:
   542  		newMap, err = newBindingsFromNames(spaceInfos, givenMap)
   543  	case idErr == nil && namesErr != nil:
   544  		newMap, err = newBindingsFromIDs(spaceInfos, givenMap)
   545  	default:
   546  		logger.Errorf("%s", namesErr)
   547  		logger.Errorf("%s", idErr)
   548  		return nil, errors.NotFoundf("space")
   549  	}
   550  
   551  	return &Bindings{st: st, bindingsMap: newMap}, err
   552  }
   553  
   554  func allOfOne(foundValue func(string) bool, givenMap map[string]string, allowEmptyValues bool) error {
   555  	for k, v := range givenMap {
   556  		if !foundValue(v) && (v != "" || (v == "" && !allowEmptyValues)) {
   557  			return errors.NotFoundf("endpoint %q, value %q, space name or id", k, v)
   558  		}
   559  	}
   560  	return nil
   561  }
   562  
   563  func newBindingsFromNames(spaceInfos network.SpaceInfos, givenMap map[string]string) (map[string]string, error) {
   564  	newMap := make(map[string]string, len(givenMap))
   565  	for epName, name := range givenMap {
   566  		if name == "" {
   567  			newMap[epName] = network.AlphaSpaceId
   568  			continue
   569  		}
   570  		// check that the name is valid and get id.
   571  		info := spaceInfos.GetByName(name)
   572  		if info == nil {
   573  			return nil, errors.NotFoundf("programming error: epName %q space name value %q", epName, name)
   574  		}
   575  		newMap[epName] = info.ID
   576  	}
   577  	return newMap, nil
   578  }
   579  
   580  func newBindingsFromIDs(spaceInfos network.SpaceInfos, givenMap map[string]string) (map[string]string, error) {
   581  	newMap := make(map[string]string, len(givenMap))
   582  	for epName, id := range givenMap {
   583  		if id == "" {
   584  			// This is most probably a set of bindings to space names.
   585  			return nil, errors.NotValidf("bindings map with empty space ID")
   586  		}
   587  		// check that the id is valid.
   588  		if !spaceInfos.ContainsID(id) {
   589  			return nil, errors.NotFoundf("programming error: epName %q space id value %q", epName, id)
   590  		}
   591  
   592  		newMap[epName] = id
   593  	}
   594  	return newMap, nil
   595  }
   596  
   597  // MapWithSpaceNames returns the current bindingMap with space names rather than ids.
   598  func (b *Bindings) MapWithSpaceNames(lookup network.SpaceInfos) (map[string]string, error) {
   599  	// Handle the fact that space name lookup can be nil or empty.
   600  	if lookup == nil || (len(b.bindingsMap) > 0 && len(lookup) == 0) {
   601  		return nil, errors.NotValidf("empty space lookup")
   602  	}
   603  
   604  	retVal := make(map[string]string, len(b.bindingsMap))
   605  
   606  	// Assume that b.bindings is always in space id format due to
   607  	// Bindings constructor.
   608  	for k, v := range b.bindingsMap {
   609  		spaceInfo := lookup.GetByID(v)
   610  		if spaceInfo == nil {
   611  			return nil, errors.NotFoundf("space with ID %q", v)
   612  		}
   613  		retVal[k] = string(spaceInfo.Name)
   614  	}
   615  	return retVal, nil
   616  }
   617  
   618  // Map returns the current bindingMap with space ids.
   619  func (b *Bindings) Map() map[string]string {
   620  	return b.bindingsMap
   621  }