github.com/vmware/transport-go@v1.3.4/bus/store.go (about)

     1  // Copyright 2019-2020 VMware, Inc.
     2  // SPDX-License-Identifier: BSD-2-Clause
     3  
     4  package bus
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"github.com/google/uuid"
    10  	"github.com/vmware/transport-go/log"
    11  	"github.com/vmware/transport-go/model"
    12  	"reflect"
    13  	"sync"
    14  )
    15  
    16  // Describes a single store item change
    17  type StoreChange struct {
    18  	Id             string      // the id of the updated item
    19  	Value          interface{} // the updated value of the item
    20  	State          interface{} // state associated with this change
    21  	IsDeleteChange bool        // true if the item was removed from the store
    22  	StoreVersion   int64       // the store's version when this change was made
    23  }
    24  
    25  // BusStore is a stateful in memory cache for objects. All state changes (any time the cache is modified)
    26  // will broadcast that updated object to any subscribers of the BusStore for those specific objects
    27  // or all objects of a certain type and state changes.
    28  type BusStore interface {
    29  	// Get the name (the id) of the store.
    30  	GetName() string
    31  	// Add new or updates existing item in the store.
    32  	Put(id string, value interface{}, state interface{})
    33  	// Returns an item from the store and a boolean flag
    34  	// indicating whether the item exists
    35  	Get(id string) (interface{}, bool)
    36  	// Shorten version of the Get() method, returns only the item value.
    37  	GetValue(id string) interface{}
    38  	// Remove an item from the store. Returns true if the remove operation was successful.
    39  	Remove(id string, state interface{}) bool
    40  	// Return a slice containing all store items.
    41  	AllValues() []interface{}
    42  	// Return a map with all items from the store.
    43  	AllValuesAsMap() map[string]interface{}
    44  	// Return a map with all items from the store with the current store version.
    45  	AllValuesAndVersion() (map[string]interface{}, int64)
    46  	// Subscribe to state changes for a specific object.
    47  	OnChange(id string, state ...interface{}) StoreStream
    48  	// Subscribe to state changes for all objects
    49  	OnAllChanges(state ...interface{}) StoreStream
    50  	// Notify when the store has been initialize (via populate() or initialize()
    51  	WhenReady(readyFunction func())
    52  	// Populate the store with a map of items and their ID's.
    53  	Populate(items map[string]interface{}) error
    54  	// Mark the store as initialized and notify all watchers.
    55  	Initialize()
    56  	// Subscribe to mutation requests made via mutate() method.
    57  	OnMutationRequest(mutationType ...interface{}) MutationStoreStream
    58  	// Send a mutation request to any subscribers handling mutations.
    59  	Mutate(request interface{}, requestType interface{},
    60  		successHandler func(interface{}), errorHandler func(interface{}))
    61  	// Removes all items from the store and change its state to uninitialized".
    62  	Reset()
    63  	// Returns true if this is galactic store.
    64  	IsGalactic() bool
    65  	// Get the item type if such is specified during the creation of the
    66  	// store
    67  	GetItemType() reflect.Type
    68  }
    69  
    70  // Internal BusStore implementation
    71  type busStore struct {
    72  	name                string
    73  	itemsLock           sync.RWMutex
    74  	items               map[string]interface{}
    75  	storeVersion        int64
    76  	storeStreamsLock    sync.RWMutex
    77  	storeStreams        []*storeStream
    78  	mutationStreamsLock sync.RWMutex
    79  	mutationStreams     []*mutationStoreStream
    80  	initializer         sync.Once
    81  	readyC              chan struct{}
    82  	isGalactic          bool
    83  	galacticConf        *galacticStoreConfig
    84  	bus                 EventBus
    85  	itemType            reflect.Type
    86  	storeSynHandler     MessageHandler
    87  }
    88  
    89  type galacticStoreConfig struct {
    90  	syncChannelConfig *storeSyncChannelConfig
    91  }
    92  
    93  func newBusStore(name string, bus EventBus, itemType reflect.Type, galacticConf *galacticStoreConfig) BusStore {
    94  
    95  	store := new(busStore)
    96  	store.name = name
    97  	store.bus = bus
    98  	store.itemType = itemType
    99  	store.galacticConf = galacticConf
   100  
   101  	initStore(store)
   102  
   103  	store.isGalactic = galacticConf != nil
   104  
   105  	if store.isGalactic {
   106  		initGalacticStore(store)
   107  	}
   108  
   109  	return store
   110  }
   111  
   112  func initStore(store *busStore) {
   113  	store.readyC = make(chan struct{})
   114  	store.storeStreams = []*storeStream{}
   115  	store.mutationStreams = []*mutationStoreStream{}
   116  	store.items = make(map[string]interface{})
   117  	store.storeVersion = 1
   118  	store.initializer = sync.Once{}
   119  }
   120  
   121  func initGalacticStore(store *busStore) {
   122  
   123  	syncChannelConf := store.galacticConf.syncChannelConfig
   124  
   125  	var err error
   126  	store.storeSynHandler, err = store.bus.ListenStream(syncChannelConf.syncChannelName)
   127  	if err != nil {
   128  		return
   129  	}
   130  
   131  	store.storeSynHandler.Handle(
   132  		func(msg *model.Message) {
   133  			d := msg.Payload.([]byte)
   134  			var storeResponse map[string]interface{}
   135  
   136  			err := json.Unmarshal(d, &storeResponse)
   137  			if err != nil {
   138  				log.Warn("failed to unmarshal storeResponse")
   139  				return
   140  			}
   141  
   142  			if storeResponse["storeId"] != store.GetName() {
   143  				// the response is for another store
   144  				return
   145  			}
   146  
   147  			responseType := storeResponse["responseType"].(string)
   148  
   149  			switch responseType {
   150  			case "storeContentResponse":
   151  
   152  				store.itemsLock.Lock()
   153  				defer store.itemsLock.Unlock()
   154  
   155  				store.updateVersionFromResponse(storeResponse)
   156  				items := storeResponse["items"].(map[string]interface{})
   157  				store.items = make(map[string]interface{})
   158  				for key, val := range items {
   159  					deserializedValue, err := store.deserializeRawValue(val)
   160  					if err != nil {
   161  						log.Warn("failed to deserialize store item value %e", err)
   162  						continue
   163  					} else {
   164  						store.items[key] = deserializedValue
   165  					}
   166  				}
   167  				store.Initialize()
   168  			case "updateStoreResponse":
   169  
   170  				store.itemsLock.Lock()
   171  				defer store.itemsLock.Unlock()
   172  
   173  				store.updateVersionFromResponse(storeResponse)
   174  				newItemRaw, ok := storeResponse["newItemValue"]
   175  				itemId := storeResponse["itemId"].(string)
   176  				if !ok || newItemRaw == nil {
   177  					store.removeInternal(itemId, "galacticSyncRemove")
   178  				} else {
   179  					newItemValue, err := store.deserializeRawValue(newItemRaw)
   180  					if err != nil {
   181  						log.Warn("failed to deserialize store item value %e", err)
   182  						return
   183  					}
   184  					store.putInternal(itemId, newItemValue, "galacticSyncUpdate")
   185  				}
   186  			}
   187  		},
   188  		func(e error) {
   189  		})
   190  
   191  	store.sendOpenStoreRequest()
   192  }
   193  
   194  func (store *busStore) updateVersionFromResponse(storeResponse map[string]interface{}) {
   195  	version := storeResponse["storeVersion"]
   196  	switch version.(type) {
   197  	case float64:
   198  		store.storeVersion = int64(version.(float64))
   199  	case int64:
   200  		store.storeVersion = version.(int64)
   201  	default:
   202  		log.Warn("failed to deserialize store version")
   203  		store.storeVersion = 1
   204  	}
   205  }
   206  
   207  func (store *busStore) deserializeRawValue(rawValue interface{}) (interface{}, error) {
   208  	return model.ConvertValueToType(rawValue, store.itemType)
   209  }
   210  
   211  func (store *busStore) sendOpenStoreRequest() {
   212  	openStoreReq := map[string]string{
   213  		"storeId": store.GetName(),
   214  	}
   215  	store.sendGalacticRequest("openStore", openStoreReq)
   216  }
   217  
   218  func (store *busStore) sendGalacticRequest(requestCmd string, requestPayload interface{}) {
   219  	// create request
   220  	id := uuid.New()
   221  	r := &model.Request{}
   222  	r.Request = requestCmd
   223  	r.Payload = requestPayload
   224  	r.Id = &id
   225  	jsonReq, _ := json.Marshal(r)
   226  
   227  	syncChannelConfig := store.galacticConf.syncChannelConfig
   228  
   229  	// send request.
   230  	syncChannelConfig.conn.SendJSONMessage(
   231  		syncChannelConfig.pubPrefix+syncChannelConfig.syncChannelName,
   232  		jsonReq)
   233  }
   234  
   235  func (store *busStore) sendCloseStoreRequest() {
   236  	closeStoreReq := map[string]string{
   237  		"storeId": store.GetName(),
   238  	}
   239  	store.sendGalacticRequest("closeStore", closeStoreReq)
   240  }
   241  
   242  func (store *busStore) OnDestroy() {
   243  	if store.IsGalactic() {
   244  		store.sendCloseStoreRequest()
   245  		if store.storeSynHandler != nil {
   246  			store.storeSynHandler.Close()
   247  		}
   248  	}
   249  }
   250  
   251  func (store *busStore) IsGalactic() bool {
   252  	return store.isGalactic
   253  }
   254  
   255  func (store *busStore) GetItemType() reflect.Type {
   256  	return store.itemType
   257  }
   258  
   259  func (store *busStore) GetName() string {
   260  	return store.name
   261  }
   262  
   263  func (store *busStore) Populate(items map[string]interface{}) error {
   264  	if store.IsGalactic() {
   265  		return fmt.Errorf("populate() API is not supported for galactic stores")
   266  	}
   267  
   268  	store.itemsLock.Lock()
   269  	defer store.itemsLock.Unlock()
   270  
   271  	if len(store.items) > 0 {
   272  		return fmt.Errorf("store items already initialized")
   273  	}
   274  
   275  	for k, v := range items {
   276  		store.items[k] = v
   277  	}
   278  	store.Initialize()
   279  	return nil
   280  }
   281  
   282  func (store *busStore) Put(id string, value interface{}, state interface{}) {
   283  	if store.IsGalactic() {
   284  		store.putGalactic(id, value)
   285  	} else {
   286  		store.itemsLock.Lock()
   287  		defer store.itemsLock.Unlock()
   288  
   289  		store.putInternal(id, value, state)
   290  	}
   291  }
   292  
   293  func (store *busStore) putGalactic(id string, value interface{}) {
   294  	store.itemsLock.RLock()
   295  	clientStoreVersion := store.storeVersion
   296  	store.itemsLock.RUnlock()
   297  
   298  	store.sendUpdateStoreRequest(id, value, clientStoreVersion)
   299  }
   300  
   301  func (store *busStore) sendUpdateStoreRequest(id string, value interface{}, storeVersion int64) {
   302  	updateReq := map[string]interface{}{
   303  		"storeId":            store.GetName(),
   304  		"clientStoreVersion": storeVersion,
   305  		"itemId":             id,
   306  		"newItemValue":       value,
   307  	}
   308  
   309  	store.sendGalacticRequest("updateStore", updateReq)
   310  }
   311  
   312  func (store *busStore) putInternal(id string, value interface{}, state interface{}) {
   313  	if !store.IsGalactic() {
   314  		store.storeVersion++
   315  	}
   316  	store.items[id] = value
   317  
   318  	change := &StoreChange{
   319  		Id:           id,
   320  		State:        state,
   321  		Value:        value,
   322  		StoreVersion: store.storeVersion,
   323  	}
   324  
   325  	go store.onStoreChange(change)
   326  }
   327  
   328  func (store *busStore) Get(id string) (interface{}, bool) {
   329  	store.itemsLock.RLock()
   330  	defer store.itemsLock.RUnlock()
   331  
   332  	val, ok := store.items[id]
   333  
   334  	return val, ok
   335  }
   336  
   337  func (store *busStore) GetValue(id string) interface{} {
   338  	val, _ := store.Get(id)
   339  	return val
   340  }
   341  
   342  func (store *busStore) Remove(id string, state interface{}) bool {
   343  	if store.IsGalactic() {
   344  		return store.removeGalactic(id)
   345  	} else {
   346  		store.itemsLock.Lock()
   347  		defer store.itemsLock.Unlock()
   348  
   349  		return store.removeInternal(id, state)
   350  	}
   351  }
   352  
   353  func (store *busStore) removeGalactic(id string) bool {
   354  	store.itemsLock.RLock()
   355  	_, ok := store.items[id]
   356  	storeVersion := store.storeVersion
   357  	store.itemsLock.RUnlock()
   358  
   359  	if ok {
   360  		store.sendUpdateStoreRequest(id, nil, storeVersion)
   361  		return true
   362  	}
   363  	return false
   364  }
   365  
   366  func (store *busStore) removeInternal(id string, state interface{}) bool {
   367  	value, ok := store.items[id]
   368  	if !ok {
   369  		return false
   370  	}
   371  
   372  	if !store.IsGalactic() {
   373  		store.storeVersion++
   374  	}
   375  	delete(store.items, id)
   376  
   377  	change := &StoreChange{
   378  		Id:             id,
   379  		State:          state,
   380  		Value:          value,
   381  		StoreVersion:   store.storeVersion,
   382  		IsDeleteChange: true,
   383  	}
   384  
   385  	go store.onStoreChange(change)
   386  	return true
   387  }
   388  
   389  func (store *busStore) AllValues() []interface{} {
   390  
   391  	store.itemsLock.RLock()
   392  	defer store.itemsLock.RUnlock()
   393  
   394  	values := make([]interface{}, 0, len(store.items))
   395  	for _, value := range store.items {
   396  		values = append(values, value)
   397  	}
   398  
   399  	return values
   400  }
   401  
   402  func (store *busStore) AllValuesAsMap() map[string]interface{} {
   403  	store.itemsLock.RLock()
   404  	defer store.itemsLock.RUnlock()
   405  
   406  	values := make(map[string]interface{})
   407  
   408  	for key, value := range store.items {
   409  		values[key] = value
   410  	}
   411  
   412  	return values
   413  }
   414  
   415  func (store *busStore) AllValuesAndVersion() (map[string]interface{}, int64) {
   416  	store.itemsLock.RLock()
   417  	defer store.itemsLock.RUnlock()
   418  
   419  	values := make(map[string]interface{})
   420  
   421  	for key, value := range store.items {
   422  		values[key] = value
   423  	}
   424  
   425  	return values, store.storeVersion
   426  }
   427  
   428  func (store *busStore) OnMutationRequest(requestType ...interface{}) MutationStoreStream {
   429  	return newMutationStoreStream(store, &mutationStreamFilter{
   430  		requestTypes: requestType,
   431  	})
   432  }
   433  
   434  func (store *busStore) Mutate(request interface{}, requestType interface{},
   435  	successHandler func(interface{}), errorHandler func(interface{})) {
   436  
   437  	store.mutationStreamsLock.RLock()
   438  	defer store.mutationStreamsLock.RUnlock()
   439  
   440  	for _, ms := range store.mutationStreams {
   441  		ms.onMutationRequest(&MutationRequest{
   442  			Request:        request,
   443  			RequestType:    requestType,
   444  			SuccessHandler: successHandler,
   445  			ErrorHandler:   errorHandler,
   446  		})
   447  	}
   448  }
   449  
   450  func (store *busStore) onStoreChange(change *StoreChange) {
   451  	store.storeStreamsLock.RLock()
   452  	defer store.storeStreamsLock.RUnlock()
   453  
   454  	for _, storeStream := range store.storeStreams {
   455  		storeStream.onStoreChange(change)
   456  	}
   457  }
   458  
   459  func (store *busStore) Initialize() {
   460  	store.initializer.Do(func() {
   461  		close(store.readyC)
   462  		store.bus.SendMonitorEvent(StoreInitializedEvt, store.name, nil)
   463  	})
   464  }
   465  
   466  func (store *busStore) Reset() {
   467  	store.itemsLock.Lock()
   468  	defer store.itemsLock.Unlock()
   469  
   470  	store.mutationStreamsLock.Lock()
   471  	defer store.mutationStreamsLock.Unlock()
   472  
   473  	store.storeStreamsLock.Lock()
   474  	defer store.storeStreamsLock.Unlock()
   475  
   476  	initStore(store)
   477  
   478  	if store.IsGalactic() {
   479  		store.sendOpenStoreRequest()
   480  	}
   481  }
   482  
   483  func (store *busStore) WhenReady(readyFunc func()) {
   484  	go func() {
   485  		<-store.readyC
   486  		readyFunc()
   487  	}()
   488  }
   489  
   490  func (store *busStore) OnChange(id string, state ...interface{}) StoreStream {
   491  	return newStoreStream(store, &streamFilter{
   492  		itemId: id,
   493  		states: state,
   494  	})
   495  }
   496  
   497  func (store *busStore) OnAllChanges(state ...interface{}) StoreStream {
   498  	return newStoreStream(store, &streamFilter{
   499  		states:        state,
   500  		matchAllItems: true,
   501  	})
   502  }
   503  
   504  func (store *busStore) onStreamSubscribe(stream *storeStream) {
   505  	store.storeStreamsLock.Lock()
   506  	defer store.storeStreamsLock.Unlock()
   507  
   508  	store.storeStreams = append(store.storeStreams, stream)
   509  }
   510  
   511  func (store *busStore) onMutationStreamSubscribe(stream *mutationStoreStream) {
   512  	store.mutationStreamsLock.Lock()
   513  	defer store.mutationStreamsLock.Unlock()
   514  
   515  	store.mutationStreams = append(store.mutationStreams, stream)
   516  }
   517  
   518  func (store *busStore) onStreamUnsubscribe(stream *storeStream) {
   519  	store.storeStreamsLock.Lock()
   520  	defer store.storeStreamsLock.Unlock()
   521  
   522  	var i int
   523  	var s *storeStream
   524  	for i, s = range store.storeStreams {
   525  		if s == stream {
   526  			break
   527  		}
   528  	}
   529  
   530  	if s == stream {
   531  		n := len(store.storeStreams)
   532  		store.storeStreams[i] = store.storeStreams[n-1]
   533  		store.storeStreams = store.storeStreams[:n-1]
   534  	}
   535  }
   536  
   537  func (store *busStore) onMutationStreamUnsubscribe(stream *mutationStoreStream) {
   538  	store.mutationStreamsLock.Lock()
   539  	defer store.mutationStreamsLock.Unlock()
   540  
   541  	var i int
   542  	var s *mutationStoreStream
   543  	for i, s = range store.mutationStreams {
   544  		if s == stream {
   545  			break
   546  		}
   547  	}
   548  
   549  	if s == stream {
   550  		n := len(store.mutationStreams)
   551  		store.mutationStreams[i] = store.mutationStreams[n-1]
   552  		store.mutationStreams = store.mutationStreams[:n-1]
   553  	}
   554  }