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 }