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

     1  // Copyright 2019-2020 VMware, Inc.
     2  // SPDX-License-Identifier: BSD-2-Clause
     3  
     4  package bus
     5  
     6  import (
     7  	"github.com/google/uuid"
     8  	"github.com/vmware/transport-go/model"
     9  	"strings"
    10  	"sync"
    11  )
    12  
    13  const (
    14  	openStoreRequest        = "openStore"
    15  	updateStoreRequest      = "updateStore"
    16  	closeStoreRequest       = "closeStore"
    17  	galacticStoreSyncUpdate = "galacticStoreSyncUpdate"
    18  	galacticStoreSyncRemove = "galacticStoreSyncRemove"
    19  )
    20  
    21  type storeSyncService struct {
    22  	bus                EventBus
    23  	lock               sync.Mutex
    24  	syncClients        map[string]*syncClientChannel
    25  	syncStoreListeners map[string]*syncStoreListener
    26  }
    27  
    28  type syncStoreListener struct {
    29  	storeStream        StoreStream
    30  	clientSyncChannels map[string]bool
    31  	lock               sync.RWMutex
    32  }
    33  
    34  type syncClientChannel struct {
    35  	channelName           string
    36  	clientRequestListener MessageHandler
    37  	openStores            map[string]bool
    38  }
    39  
    40  func newStoreSyncService(bus EventBus) *storeSyncService {
    41  	syncService := &storeSyncService{
    42  		bus:                bus,
    43  		syncClients:        make(map[string]*syncClientChannel),
    44  		syncStoreListeners: make(map[string]*syncStoreListener),
    45  	}
    46  	syncService.init()
    47  	return syncService
    48  }
    49  
    50  func (syncService *storeSyncService) init() {
    51  	syncService.bus.AddMonitorEventListener(
    52  		func(monitorEvt *MonitorEvent) {
    53  			if !strings.HasPrefix(monitorEvt.EntityName, "transport-store-sync.") {
    54  				// not a store sync channel, ignore the message
    55  				return
    56  			}
    57  
    58  			switch monitorEvt.EventType {
    59  			case FabricEndpointSubscribeEvt:
    60  				syncService.openNewClientSyncChannel(monitorEvt.EntityName)
    61  			case ChannelDestroyedEvt:
    62  				syncService.closeClientSyncChannel(monitorEvt.EntityName)
    63  			}
    64  		},
    65  		FabricEndpointSubscribeEvt, ChannelDestroyedEvt)
    66  }
    67  
    68  func (syncService *storeSyncService) openNewClientSyncChannel(channelName string) {
    69  	syncService.lock.Lock()
    70  	defer syncService.lock.Unlock()
    71  
    72  	if _, ok := syncService.syncClients[channelName]; ok {
    73  		// channel already opened.
    74  		return
    75  	}
    76  
    77  	syncClient := &syncClientChannel{
    78  		channelName: channelName,
    79  		openStores:  make(map[string]bool),
    80  	}
    81  	syncClient.clientRequestListener, _ = syncService.bus.ListenRequestStream(channelName)
    82  	if syncClient.clientRequestListener != nil {
    83  		syncClient.clientRequestListener.Handle(
    84  			func(message *model.Message) {
    85  				request, reqOk := message.Payload.(*model.Request)
    86  				if !reqOk || request.Payload == nil {
    87  					return
    88  				}
    89  				var storeRequest map[string]interface{}
    90  				storeRequest, ok := request.Payload.(map[string]interface{})
    91  				if !ok {
    92  					return
    93  				}
    94  
    95  				switch request.Request {
    96  				case openStoreRequest:
    97  					syncService.openStore(syncClient, storeRequest, request.Id)
    98  				case closeStoreRequest:
    99  					syncService.closeStore(syncClient, storeRequest, request.Id)
   100  				case updateStoreRequest:
   101  					syncService.updateStore(syncClient, storeRequest, request.Id)
   102  				}
   103  			}, func(e error) {})
   104  	}
   105  	syncService.syncClients[channelName] = syncClient
   106  }
   107  
   108  func (syncService *storeSyncService) closeClientSyncChannel(channelName string) {
   109  	syncService.lock.Lock()
   110  	defer syncService.lock.Unlock()
   111  
   112  	syncClient, ok := syncService.syncClients[channelName]
   113  	if !ok || syncClient == nil {
   114  		// client is already closed
   115  		return
   116  	}
   117  
   118  	for storeId := range syncClient.openStores {
   119  		listener := syncService.syncStoreListeners[storeId]
   120  		if listener != nil {
   121  			listener.removeChannel(channelName)
   122  			if listener.isEmpty() {
   123  				listener.unsubscribe()
   124  				delete(syncService.syncStoreListeners, storeId)
   125  			}
   126  		}
   127  	}
   128  
   129  	delete(syncService.syncClients, channelName)
   130  }
   131  
   132  func (syncService *storeSyncService) openStore(
   133  	syncClient *syncClientChannel, request map[string]interface{}, reqId *uuid.UUID) {
   134  
   135  	storeId, ok := getStingProperty("storeId", request)
   136  	if !ok || storeId == "" {
   137  		syncService.sendErrorResponse(syncClient.channelName, "Invalid OpenStoreRequest", reqId)
   138  		return
   139  	}
   140  
   141  	store := syncService.bus.GetStoreManager().GetStore(storeId)
   142  	if store == nil {
   143  		syncService.sendErrorResponse(
   144  			syncClient.channelName, "Cannot open non-existing store: "+storeId, reqId)
   145  		return
   146  	}
   147  
   148  	syncService.lock.Lock()
   149  	defer syncService.lock.Unlock()
   150  
   151  	syncClient.openStores[storeId] = true
   152  
   153  	storeListener, ok := syncService.syncStoreListeners[storeId]
   154  	if !ok {
   155  		storeListener = newSyncStoreListener(syncService.bus, store)
   156  		syncService.syncStoreListeners[storeId] = storeListener
   157  	}
   158  	storeListener.addChannel(syncClient.channelName)
   159  
   160  	store.WhenReady(func() {
   161  		items, version := store.AllValuesAndVersion()
   162  
   163  		syncService.bus.SendResponseMessage(syncClient.channelName,
   164  			model.NewStoreContentResponse(storeId, items, version), nil)
   165  	})
   166  }
   167  
   168  func (syncService *storeSyncService) closeStore(
   169  	syncClient *syncClientChannel, request map[string]interface{}, reqId *uuid.UUID) {
   170  
   171  	storeId, ok := getStingProperty("storeId", request)
   172  	if !ok || storeId == "" {
   173  		syncService.sendErrorResponse(syncClient.channelName, "Invalid CloseStoreRequest", reqId)
   174  		return
   175  	}
   176  
   177  	syncService.lock.Lock()
   178  	defer syncService.lock.Unlock()
   179  
   180  	delete(syncClient.openStores, storeId)
   181  
   182  	storeListener, ok := syncService.syncStoreListeners[storeId]
   183  	if ok && storeListener != nil {
   184  		storeListener.removeChannel(syncClient.channelName)
   185  		if storeListener.isEmpty() {
   186  			storeListener.unsubscribe()
   187  			delete(syncService.syncStoreListeners, storeId)
   188  		}
   189  	}
   190  }
   191  
   192  func (syncService *storeSyncService) updateStore(
   193  	syncClient *syncClientChannel, request map[string]interface{}, reqId *uuid.UUID) {
   194  
   195  	storeId, ok := getStingProperty("storeId", request)
   196  	if !ok || storeId == "" {
   197  		syncService.sendErrorResponse(
   198  			syncClient.channelName, "Invalid UpdateStoreRequest: missing storeId", reqId)
   199  		return
   200  	}
   201  	itemId, ok := getStingProperty("itemId", request)
   202  	if !ok || itemId == "" {
   203  		syncService.sendErrorResponse(
   204  			syncClient.channelName, "Invalid UpdateStoreRequest: missing itemId", reqId)
   205  		return
   206  	}
   207  
   208  	store := syncService.bus.GetStoreManager().GetStore(storeId)
   209  	if store == nil {
   210  		syncService.sendErrorResponse(
   211  			syncClient.channelName, "Cannot update non-existing store: "+storeId, reqId)
   212  		return
   213  	}
   214  
   215  	rawValue, ok := request["newItemValue"]
   216  	if rawValue == nil {
   217  		store.Remove(itemId, galacticStoreSyncRemove)
   218  	} else {
   219  		deserializedValue, err := model.ConvertValueToType(rawValue, store.GetItemType())
   220  		if err != nil || deserializedValue == nil {
   221  			errMsg := "Cannot deserialize UpdateStoreRequest item value"
   222  			if err != nil {
   223  				errMsg = "Cannot deserialize UpdateStoreRequest item value: " + err.Error()
   224  			}
   225  			syncService.sendErrorResponse(syncClient.channelName, errMsg, reqId)
   226  			return
   227  		}
   228  		store.Put(itemId, deserializedValue, galacticStoreSyncUpdate)
   229  	}
   230  }
   231  
   232  func getStingProperty(id string, request map[string]interface{}) (string, bool) {
   233  	propValue, ok := request[id]
   234  	if !ok || propValue == nil {
   235  		return "", false
   236  	}
   237  	stringValue, ok := propValue.(string)
   238  	return stringValue, ok
   239  }
   240  
   241  func (syncService *storeSyncService) sendErrorResponse(
   242  	clientChannel string, errorMsg string, reqId *uuid.UUID) {
   243  
   244  	syncService.bus.SendResponseMessage(clientChannel, &model.Response{
   245  		Id:           reqId,
   246  		Error:        true,
   247  		ErrorCode:    1,
   248  		ErrorMessage: errorMsg,
   249  	}, nil)
   250  }
   251  
   252  func newSyncStoreListener(bus EventBus, store BusStore) *syncStoreListener {
   253  
   254  	listener := &syncStoreListener{
   255  		storeStream:        store.OnAllChanges(),
   256  		clientSyncChannels: make(map[string]bool),
   257  	}
   258  
   259  	listener.storeStream.Subscribe(func(change *StoreChange) {
   260  		updateStoreResp := model.NewUpdateStoreResponse(
   261  			store.GetName(), change.Id, change.Value, change.StoreVersion)
   262  		if change.IsDeleteChange {
   263  			updateStoreResp.NewItemValue = nil
   264  		}
   265  
   266  		listener.lock.RLock()
   267  		defer listener.lock.RUnlock()
   268  
   269  		for chName := range listener.clientSyncChannels {
   270  			bus.SendResponseMessage(chName, updateStoreResp, nil)
   271  		}
   272  	})
   273  
   274  	return listener
   275  }
   276  
   277  func (l *syncStoreListener) unsubscribe() {
   278  
   279  	l.storeStream.Unsubscribe()
   280  }
   281  
   282  func (l *syncStoreListener) addChannel(clientChannel string) {
   283  	l.lock.Lock()
   284  	defer l.lock.Unlock()
   285  	l.clientSyncChannels[clientChannel] = true
   286  }
   287  
   288  func (l *syncStoreListener) removeChannel(clientChannel string) {
   289  	l.lock.Lock()
   290  	defer l.lock.Unlock()
   291  	delete(l.clientSyncChannels, clientChannel)
   292  }
   293  
   294  func (l *syncStoreListener) isEmpty() bool {
   295  	l.lock.Lock()
   296  	defer l.lock.Unlock()
   297  	return len(l.clientSyncChannels) == 0
   298  }