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 }