blob: 53d93b7b4ba93b3317f0f48a00b3660fef640444 [file] [log] [blame]
Matt Jeanneretcab955f2019-04-10 15:45:57 -04001/*
2 * Copyright 2018-present Open Networking Foundation
3
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7
8 * http://www.apache.org/licenses/LICENSE-2.0
9
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package model
18
19import (
20 "bytes"
21 "compress/gzip"
Manikkaraj kb1d51442019-07-23 10:41:02 -040022 "context"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040023 "github.com/golang/protobuf/proto"
Manikkaraj kb1d51442019-07-23 10:41:02 -040024 "github.com/google/uuid"
Scott Baker51290152019-10-24 14:23:20 -070025 "github.com/opencord/voltha-lib-go/v2/pkg/db/kvstore"
26 "github.com/opencord/voltha-lib-go/v2/pkg/log"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040027 "reflect"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040028 "strings"
29 "sync"
30)
31
32// PersistedRevision holds information of revision meant to be saved in a persistent storage
33type PersistedRevision struct {
34 Revision
35 Compress bool
36
Manikkaraj kb1d51442019-07-23 10:41:02 -040037 events chan *kvstore.Event
38 kvStore *Backend
39 mutex sync.RWMutex
40 versionMutex sync.RWMutex
41 Version int64
42 isStored bool
43 isWatched bool
Matt Jeanneretcab955f2019-04-10 15:45:57 -040044}
45
Matt Jeanneret384d8c92019-05-06 14:27:31 -040046type watchCache struct {
47 Cache sync.Map
48}
49
50var watchCacheInstance *watchCache
51var watchCacheOne sync.Once
52
53func Watches() *watchCache {
54 watchCacheOne.Do(func() {
55 watchCacheInstance = &watchCache{Cache: sync.Map{}}
56 })
57 return watchCacheInstance
58}
59
Matt Jeanneretcab955f2019-04-10 15:45:57 -040060// NewPersistedRevision creates a new instance of a PersistentRevision structure
61func NewPersistedRevision(branch *Branch, data interface{}, children map[string][]Revision) Revision {
62 pr := &PersistedRevision{}
63 pr.kvStore = branch.Node.GetRoot().KvStore
Manikkaraj kb1d51442019-07-23 10:41:02 -040064 pr.Version = 1
Matt Jeanneretcab955f2019-04-10 15:45:57 -040065 pr.Revision = NewNonPersistedRevision(nil, branch, data, children)
66 return pr
67}
68
Manikkaraj kb1d51442019-07-23 10:41:02 -040069func (pr *PersistedRevision) getVersion() int64 {
70 pr.versionMutex.RLock()
71 defer pr.versionMutex.RUnlock()
72 return pr.Version
73}
74
75func (pr *PersistedRevision) setVersion(version int64) {
76 pr.versionMutex.Lock()
77 defer pr.versionMutex.Unlock()
78 pr.Version = version
79}
80
Matt Jeanneretcab955f2019-04-10 15:45:57 -040081// Finalize is responsible of saving the revision in the persistent storage
82func (pr *PersistedRevision) Finalize(skipOnExist bool) {
83 pr.store(skipOnExist)
84}
85
Matt Jeanneretcab955f2019-04-10 15:45:57 -040086func (pr *PersistedRevision) store(skipOnExist bool) {
87 if pr.GetBranch().Txid != "" {
88 return
89 }
90
manikkaraj k9eb6cac2019-05-09 12:32:03 -040091 log.Debugw("ready-to-store-revision", log.Fields{"hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetData()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -040092
Manikkaraj kb1d51442019-07-23 10:41:02 -040093 // clone the revision data to avoid any race conditions with processes
94 // accessing the same data
95 cloned := proto.Clone(pr.GetConfig().Data.(proto.Message))
96
97 if blob, err := proto.Marshal(cloned); err != nil {
98 log.Errorw("problem-to-marshal", log.Fields{"error": err, "hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetData()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -040099 } else {
100 if pr.Compress {
101 var b bytes.Buffer
102 w := gzip.NewWriter(&b)
103 w.Write(blob)
104 w.Close()
105 blob = b.Bytes()
106 }
107
Manikkaraj kb1d51442019-07-23 10:41:02 -0400108 GetRevCache().Set(pr.GetName(), pr)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400109 if err := pr.kvStore.Put(pr.GetName(), blob); err != nil {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400110 log.Warnw("problem-storing-revision", log.Fields{"error": err, "hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetConfig().Data})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400111 } else {
Manikkaraj kb1d51442019-07-23 10:41:02 -0400112 log.Debugw("storing-revision", log.Fields{"hash": pr.GetHash(), "name": pr.GetName(), "data": pr.GetConfig().Data, "version": pr.getVersion()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400113 pr.isStored = true
114 }
115 }
116}
117
118func (pr *PersistedRevision) SetupWatch(key string) {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400119 if key == "" {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400120 log.Debugw("ignoring-watch", log.Fields{"key": key, "revision-hash": pr.GetHash()})
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400121 return
122 }
123
124 if _, exists := Watches().Cache.LoadOrStore(key+"-"+pr.GetHash(), struct{}{}); exists {
125 return
126 }
127
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400128 if pr.events == nil {
129 pr.events = make(chan *kvstore.Event)
130
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400131 log.Debugw("setting-watch-channel", log.Fields{"key": key, "revision-hash": pr.GetHash()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400132
133 pr.SetName(key)
134 pr.events = pr.kvStore.CreateWatch(key)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400135 }
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400136
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400137 if !pr.isWatched {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400138 pr.isWatched = true
139
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400140 log.Debugw("setting-watch-routine", log.Fields{"key": key, "revision-hash": pr.GetHash()})
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400141
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400142 // Start watching
143 go pr.startWatching()
144 }
145}
146
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400147func (pr *PersistedRevision) startWatching() {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400148 log.Debugw("starting-watch", log.Fields{"key": pr.GetHash(), "watch": pr.GetName()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400149
150StopWatchLoop:
151 for {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700152 latestRev := pr.GetBranch().GetLatest()
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400153
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400154 select {
155 case event, ok := <-pr.events:
156 if !ok {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700157 log.Errorw("event-channel-failure: stopping watch loop", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400158 break StopWatchLoop
159 }
Mahir Gunyele77977b2019-06-27 05:36:22 -0700160 log.Debugw("received-event", log.Fields{"type": event.EventType, "watch": latestRev.GetName()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400161
162 switch event.EventType {
163 case kvstore.DELETE:
Mahir Gunyele77977b2019-06-27 05:36:22 -0700164 log.Debugw("delete-from-memory", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
David Bainbridgebe7cac12019-10-23 19:53:07 +0000165
166 // Remove reference from cache
167 GetRevCache().Delete(latestRev.GetName())
168
169 // Remove reference from parent
170 parent := pr.GetBranch().Node.GetRoot()
171 parent.GetBranch(NONE).Latest.ChildDropByName(latestRev.GetName())
172
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400173 break StopWatchLoop
174
175 case kvstore.PUT:
Mahir Gunyele77977b2019-06-27 05:36:22 -0700176 log.Debugw("update-in-memory", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
Manikkaraj kb1d51442019-07-23 10:41:02 -0400177 if latestRev.getVersion() >= event.Version {
178 log.Debugw("skipping-matching-or-older-revision", log.Fields{
179 "watch": latestRev.GetName(),
180 "watch-version": event.Version,
181 "latest-version": latestRev.getVersion(),
182 })
183 continue
184 } else {
185 log.Debugw("watch-revision-is-newer", log.Fields{
186 "watch": latestRev.GetName(),
187 "watch-version": event.Version,
188 "latest-version": latestRev.getVersion(),
189 })
190 }
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400191
Mahir Gunyele77977b2019-06-27 05:36:22 -0700192 data := reflect.New(reflect.TypeOf(latestRev.GetData()).Elem())
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400193
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400194 if err := proto.Unmarshal(event.Value.([]byte), data.Interface().(proto.Message)); err != nil {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700195 log.Errorw("failed-to-unmarshal-watch-data", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName(), "error": err})
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400196 } else {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700197 log.Debugw("un-marshaled-watch-data", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName(), "data": data.Interface()})
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400198
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400199 var pathLock string
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400200 var blobs map[string]*kvstore.KVPair
201
202 // The watch reported new persistence data.
203 // Construct an object that will be used to update the memory
204 blobs = make(map[string]*kvstore.KVPair)
205 key, _ := kvstore.ToString(event.Key)
206 blobs[key] = &kvstore.KVPair{
207 Key: key,
208 Value: event.Value,
209 Session: "",
210 Lease: 0,
Manikkaraj kb1d51442019-07-23 10:41:02 -0400211 Version: event.Version,
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400212 }
213
Mahir Gunyele77977b2019-06-27 05:36:22 -0700214 if latestRev.GetNode().GetProxy() != nil {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400215 //
216 // If a proxy exists for this revision, use it to lock access to the path
217 // and prevent simultaneous updates to the object in memory
218 //
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400219
220 //If the proxy already has a request in progress, then there is no need to process the watch
Manikkaraj kb1d51442019-07-23 10:41:02 -0400221 if latestRev.GetNode().GetProxy().GetOperation() != PROXY_NONE {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400222 log.Debugw("operation-in-progress", log.Fields{
Mahir Gunyele77977b2019-06-27 05:36:22 -0700223 "key": latestRev.GetHash(),
224 "path": latestRev.GetNode().GetProxy().getFullPath(),
Manikkaraj kb1d51442019-07-23 10:41:02 -0400225 "operation": latestRev.GetNode().GetProxy().operation.String(),
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400226 })
Manikkaraj kb1d51442019-07-23 10:41:02 -0400227 continue
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400228 }
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400229
Manikkaraj kb1d51442019-07-23 10:41:02 -0400230 pathLock, _ = latestRev.GetNode().GetProxy().parseForControlledPath(latestRev.GetNode().GetProxy().getFullPath())
231
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400232 // Reserve the path to prevent others to modify while we reload from persistence
Manikkaraj kb1d51442019-07-23 10:41:02 -0400233 latestRev.GetNode().GetProxy().GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
234 latestRev.GetNode().GetProxy().SetOperation(PROXY_WATCH)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400235
236 // Load changes and apply to memory
Manikkaraj kb1d51442019-07-23 10:41:02 -0400237 latestRev.LoadFromPersistence(context.Background(), latestRev.GetName(), "", blobs)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400238
Manikkaraj kb1d51442019-07-23 10:41:02 -0400239 // Release path
240 latestRev.GetNode().GetProxy().GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400241
242 } else {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400243 // This block should be reached only if coming from a non-proxied request
Mahir Gunyele77977b2019-06-27 05:36:22 -0700244 log.Debugw("revision-with-no-proxy", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName()})
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400245
246 // Load changes and apply to memory
Manikkaraj kb1d51442019-07-23 10:41:02 -0400247 latestRev.LoadFromPersistence(context.Background(), latestRev.GetName(), "", blobs)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400248 }
249 }
250
251 default:
Mahir Gunyele77977b2019-06-27 05:36:22 -0700252 log.Debugw("unhandled-event", log.Fields{"key": latestRev.GetHash(), "watch": latestRev.GetName(), "type": event.EventType})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400253 }
254 }
255 }
256
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400257 Watches().Cache.Delete(pr.GetName() + "-" + pr.GetHash())
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400258
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400259 log.Debugw("exiting-watch", log.Fields{"key": pr.GetHash(), "watch": pr.GetName()})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400260}
261
262// UpdateData modifies the information in the data model and saves it in the persistent storage
Manikkaraj kb1d51442019-07-23 10:41:02 -0400263func (pr *PersistedRevision) UpdateData(ctx context.Context, data interface{}, branch *Branch) Revision {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400264 log.Debugw("updating-persisted-data", log.Fields{"hash": pr.GetHash()})
265
Manikkaraj kb1d51442019-07-23 10:41:02 -0400266 newNPR := pr.Revision.UpdateData(ctx, data, branch)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400267
268 newPR := &PersistedRevision{
Mahir Gunyele77977b2019-06-27 05:36:22 -0700269 Revision: newNPR,
270 Compress: pr.Compress,
271 kvStore: pr.kvStore,
272 events: pr.events,
Manikkaraj kb1d51442019-07-23 10:41:02 -0400273 Version: pr.getVersion(),
Mahir Gunyele77977b2019-06-27 05:36:22 -0700274 isWatched: pr.isWatched,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400275 }
276
277 if newPR.GetHash() != pr.GetHash() {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400278 newPR.isStored = false
279 pr.Drop(branch.Txid, false)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400280 pr.Drop(branch.Txid, false)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400281 } else {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400282 newPR.isStored = true
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400283 }
284
285 return newPR
286}
287
288// UpdateChildren modifies the children of a revision and of a specific component and saves it in the persistent storage
Manikkaraj kb1d51442019-07-23 10:41:02 -0400289func (pr *PersistedRevision) UpdateChildren(ctx context.Context, name string, children []Revision, branch *Branch) Revision {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400290 log.Debugw("updating-persisted-children", log.Fields{"hash": pr.GetHash()})
291
Manikkaraj kb1d51442019-07-23 10:41:02 -0400292 newNPR := pr.Revision.UpdateChildren(ctx, name, children, branch)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400293
294 newPR := &PersistedRevision{
Mahir Gunyele77977b2019-06-27 05:36:22 -0700295 Revision: newNPR,
296 Compress: pr.Compress,
297 kvStore: pr.kvStore,
298 events: pr.events,
Manikkaraj kb1d51442019-07-23 10:41:02 -0400299 Version: pr.getVersion(),
Mahir Gunyele77977b2019-06-27 05:36:22 -0700300 isWatched: pr.isWatched,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400301 }
302
303 if newPR.GetHash() != pr.GetHash() {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400304 newPR.isStored = false
305 pr.Drop(branch.Txid, false)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400306 } else {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400307 newPR.isStored = true
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400308 }
309
310 return newPR
311}
312
313// UpdateAllChildren modifies the children for all components of a revision and saves it in the peristent storage
314func (pr *PersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
315 log.Debugw("updating-all-persisted-children", log.Fields{"hash": pr.GetHash()})
316
317 newNPR := pr.Revision.UpdateAllChildren(children, branch)
318
319 newPR := &PersistedRevision{
Mahir Gunyele77977b2019-06-27 05:36:22 -0700320 Revision: newNPR,
321 Compress: pr.Compress,
322 kvStore: pr.kvStore,
323 events: pr.events,
Manikkaraj kb1d51442019-07-23 10:41:02 -0400324 Version: pr.getVersion(),
Mahir Gunyele77977b2019-06-27 05:36:22 -0700325 isWatched: pr.isWatched,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400326 }
327
328 if newPR.GetHash() != pr.GetHash() {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400329 newPR.isStored = false
330 pr.Drop(branch.Txid, false)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400331 } else {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400332 newPR.isStored = true
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400333 }
334
335 return newPR
336}
337
338// Drop takes care of eliminating a revision hash that is no longer needed
339// and its associated config when required
340func (pr *PersistedRevision) Drop(txid string, includeConfig bool) {
341 pr.Revision.Drop(txid, includeConfig)
342}
343
344// Drop takes care of eliminating a revision hash that is no longer needed
345// and its associated config when required
346func (pr *PersistedRevision) StorageDrop(txid string, includeConfig bool) {
Manikkaraj kb1d51442019-07-23 10:41:02 -0400347 log.Debugw("dropping-revision", log.Fields{"txid": txid, "hash": pr.GetHash(), "config-hash": pr.GetConfig().Hash})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400348
349 pr.mutex.Lock()
350 defer pr.mutex.Unlock()
351 if pr.kvStore != nil && txid == "" {
352 if pr.isStored {
353 if pr.isWatched {
354 pr.kvStore.DeleteWatch(pr.GetName(), pr.events)
355 pr.isWatched = false
356 }
357
358 if err := pr.kvStore.Delete(pr.GetName()); err != nil {
359 log.Errorw("failed-to-remove-revision", log.Fields{"hash": pr.GetHash(), "error": err.Error()})
360 } else {
361 pr.isStored = false
362 }
363 }
364
365 } else {
366 if includeConfig {
367 log.Debugw("attempted-to-remove-transacted-revision-config", log.Fields{"hash": pr.GetConfig().Hash, "txid": txid})
368 }
369 log.Debugw("attempted-to-remove-transacted-revision", log.Fields{"hash": pr.GetHash(), "txid": txid})
370 }
371
372 pr.Revision.Drop(txid, includeConfig)
373}
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400374
375// verifyPersistedEntry validates if the provided data is available or not in memory and applies updates as required
Manikkaraj kb1d51442019-07-23 10:41:02 -0400376func (pr *PersistedRevision) verifyPersistedEntry(ctx context.Context, data interface{}, typeName string, keyName string,
377 keyValue string, txid string, version int64) (response Revision) {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400378 // Parent which holds the current node entry
Manikkaraj kb1d51442019-07-23 10:41:02 -0400379 parent := pr.GetBranch().Node.GetRoot()
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400380
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400381 // Get a copy of the parent's children
382 children := make([]Revision, len(parent.GetBranch(NONE).Latest.GetChildren(typeName)))
383 copy(children, parent.GetBranch(NONE).Latest.GetChildren(typeName))
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400384
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400385 // Verify if a child with the provided key value can be found
386 if childIdx, childRev := pr.GetNode().findRevByKey(children, keyName, keyValue); childRev != nil {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400387 // A child matching the provided key exists in memory
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400388 // Verify if the data differs from what was retrieved from persistence
Mahir Gunyele77977b2019-06-27 05:36:22 -0700389 // Also check if we are treating a newer revision of the data or not
Manikkaraj kb1d51442019-07-23 10:41:02 -0400390 if childRev.GetData().(proto.Message).String() != data.(proto.Message).String() && childRev.getVersion() < version {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400391 log.Debugw("revision-data-is-different", log.Fields{
David Bainbridgebe7cac12019-10-23 19:53:07 +0000392 "key": childRev.GetHash(),
393 "name": childRev.GetName(),
394 "data": childRev.GetData(),
395 "in-memory-version": childRev.getVersion(),
396 "persisted-version": version,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400397 })
398
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400399 //
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400400 // Data has changed; replace the child entry and update the parent revision
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400401 //
402
403 // BEGIN Lock child -- prevent any incoming changes
404 childRev.GetBranch().LatestLock.Lock()
405
406 // Update child
Manikkaraj kb1d51442019-07-23 10:41:02 -0400407 updatedChildRev := childRev.UpdateData(ctx, data, childRev.GetBranch())
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400408
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400409 updatedChildRev.GetNode().SetProxy(childRev.GetNode().GetProxy())
410 updatedChildRev.SetupWatch(updatedChildRev.GetName())
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400411 updatedChildRev.SetLastUpdate()
Manikkaraj kb1d51442019-07-23 10:41:02 -0400412 updatedChildRev.(*PersistedRevision).setVersion(version)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400413
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400414 // Update cache
Manikkaraj kb1d51442019-07-23 10:41:02 -0400415 GetRevCache().Set(updatedChildRev.GetName(), updatedChildRev)
Mahir Gunyele77977b2019-06-27 05:36:22 -0700416 childRev.Drop(txid, false)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400417
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400418 childRev.GetBranch().LatestLock.Unlock()
419 // END lock child
420
421 // Update child entry
422 children[childIdx] = updatedChildRev
423
424 // BEGIN lock parent -- Update parent
425 parent.GetBranch(NONE).LatestLock.Lock()
426
Manikkaraj kb1d51442019-07-23 10:41:02 -0400427 updatedRev := parent.GetBranch(NONE).GetLatest().UpdateChildren(ctx, typeName, children, parent.GetBranch(NONE))
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400428 parent.GetBranch(NONE).Node.makeLatest(parent.GetBranch(NONE), updatedRev, nil)
429
430 parent.GetBranch(NONE).LatestLock.Unlock()
431 // END lock parent
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400432
433 // Drop the previous child revision
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400434 parent.GetBranch(NONE).Latest.ChildDrop(typeName, childRev.GetHash())
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400435
436 if updatedChildRev != nil {
437 log.Debugw("verify-persisted-entry--adding-child", log.Fields{
438 "key": updatedChildRev.GetHash(),
439 "name": updatedChildRev.GetName(),
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400440 "data": updatedChildRev.GetData(),
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400441 })
442 response = updatedChildRev
443 }
444 } else {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400445 if childRev != nil {
Manikkaraj kb1d51442019-07-23 10:41:02 -0400446 log.Debugw("keeping-revision-data", log.Fields{
David Bainbridgebe7cac12019-10-23 19:53:07 +0000447 "key": childRev.GetHash(),
448 "name": childRev.GetName(),
449 "data": childRev.GetData(),
450 "in-memory-version": childRev.getVersion(),
451 "persistence-version": version,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400452 })
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400453
454 // Update timestamp to reflect when it was last read and to reset tracked timeout
455 childRev.SetLastUpdate()
Manikkaraj kb1d51442019-07-23 10:41:02 -0400456 if childRev.getVersion() < version {
457 childRev.(*PersistedRevision).setVersion(version)
458 }
459 GetRevCache().Set(childRev.GetName(), childRev)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400460 response = childRev
461 }
462 }
Mahir Gunyele77977b2019-06-27 05:36:22 -0700463
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400464 } else {
465 // There is no available child with that key value.
466 // Create a new child and update the parent revision.
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400467 log.Debugw("no-such-revision-entry", log.Fields{
David Bainbridgebe7cac12019-10-23 19:53:07 +0000468 "key": keyValue,
469 "name": typeName,
470 "data": data,
471 "version": version,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400472 })
473
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400474 // BEGIN child lock
475 pr.GetBranch().LatestLock.Lock()
476
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400477 // Construct a new child node with the retrieved persistence data
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400478 childRev = pr.GetBranch().Node.MakeNode(data, txid).Latest(txid)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400479
480 // We need to start watching this entry for future changes
481 childRev.SetName(typeName + "/" + keyValue)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400482 childRev.SetupWatch(childRev.GetName())
Manikkaraj kb1d51442019-07-23 10:41:02 -0400483 childRev.(*PersistedRevision).setVersion(version)
484
485 // Add entry to cache
486 GetRevCache().Set(childRev.GetName(), childRev)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400487
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400488 pr.GetBranch().LatestLock.Unlock()
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400489 // END child lock
490
491 //
492 // Add the child to the parent revision
493 //
494
495 // BEGIN parent lock
496 parent.GetBranch(NONE).LatestLock.Lock()
497 children = append(children, childRev)
Manikkaraj kb1d51442019-07-23 10:41:02 -0400498 updatedRev := parent.GetBranch(NONE).GetLatest().UpdateChildren(ctx, typeName, children, parent.GetBranch(NONE))
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400499 updatedRev.GetNode().SetProxy(parent.GetBranch(NONE).Node.GetProxy())
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400500 parent.GetBranch(NONE).Node.makeLatest(parent.GetBranch(NONE), updatedRev, nil)
501 parent.GetBranch(NONE).LatestLock.Unlock()
502 // END parent lock
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400503
504 // Child entry is valid and can be included in the response object
505 if childRev != nil {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400506 log.Debugw("adding-revision-to-response", log.Fields{
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400507 "key": childRev.GetHash(),
508 "name": childRev.GetName(),
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400509 "data": childRev.GetData(),
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400510 })
511 response = childRev
512 }
513 }
514
515 return response
516}
517
518// LoadFromPersistence retrieves data from kv store at the specified location and refreshes the memory
519// by adding missing entries, updating changed entries and ignoring unchanged ones
Manikkaraj kb1d51442019-07-23 10:41:02 -0400520func (pr *PersistedRevision) LoadFromPersistence(ctx context.Context, path string, txid string, blobs map[string]*kvstore.KVPair) []Revision {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400521 pr.mutex.Lock()
522 defer pr.mutex.Unlock()
523
524 log.Debugw("loading-from-persistence", log.Fields{"path": path, "txid": txid})
525
526 var response []Revision
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400527
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400528 for strings.HasPrefix(path, "/") {
529 path = path[1:]
530 }
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400531
532 if pr.kvStore != nil && path != "" {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400533 if blobs == nil || len(blobs) == 0 {
534 log.Debugw("retrieve-from-kv", log.Fields{"path": path, "txid": txid})
535 blobs, _ = pr.kvStore.List(path)
536 }
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400537
538 partition := strings.SplitN(path, "/", 2)
539 name := partition[0]
540
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400541 var nodeType interface{}
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400542 if len(partition) < 2 {
543 path = ""
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400544 nodeType = pr.GetBranch().Node.Type
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400545 } else {
546 path = partition[1]
Manikkaraj kb1d51442019-07-23 10:41:02 -0400547 nodeType = pr.GetBranch().Node.GetRoot().Type
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400548 }
549
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400550 field := ChildrenFields(nodeType)[name]
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400551
552 if field != nil && field.IsContainer {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400553 log.Debugw("parsing-data-blobs", log.Fields{
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400554 "path": path,
555 "name": name,
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400556 "size": len(blobs),
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400557 })
558
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400559 for _, blob := range blobs {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400560 output := blob.Value.([]byte)
561
562 data := reflect.New(field.ClassType.Elem())
563
564 if err := proto.Unmarshal(output, data.Interface().(proto.Message)); err != nil {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400565 log.Errorw("failed-to-unmarshal", log.Fields{
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400566 "path": path,
567 "txid": txid,
568 "error": err,
569 })
570 } else if path == "" {
571 if field.Key != "" {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400572 log.Debugw("no-path-with-container-key", log.Fields{
573 "path": path,
574 "txid": txid,
575 "data": data.Interface(),
576 })
577
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400578 // Retrieve the key identifier value from the data structure
579 // based on the field's key attribute
580 _, key := GetAttributeValue(data.Interface(), field.Key, 0)
581
Manikkaraj kb1d51442019-07-23 10:41:02 -0400582 if entry := pr.verifyPersistedEntry(ctx, data.Interface(), name, field.Key, key.String(), txid, blob.Version); entry != nil {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400583 response = append(response, entry)
584 }
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400585 } else {
586 log.Debugw("path-with-no-container-key", log.Fields{
587 "path": path,
588 "txid": txid,
589 "data": data.Interface(),
590 })
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400591 }
592
593 } else if field.Key != "" {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400594 log.Debugw("path-with-container-key", log.Fields{
595 "path": path,
596 "txid": txid,
597 "data": data.Interface(),
598 })
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400599 // The request is for a specific entry/id
600 partition := strings.SplitN(path, "/", 2)
601 key := partition[0]
602 if len(partition) < 2 {
603 path = ""
604 } else {
605 path = partition[1]
606 }
607 keyValue := field.KeyFromStr(key)
608
Manikkaraj kb1d51442019-07-23 10:41:02 -0400609 if entry := pr.verifyPersistedEntry(ctx, data.Interface(), name, field.Key, keyValue.(string), txid, blob.Version); entry != nil {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400610 response = append(response, entry)
611 }
612 }
613 }
614
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400615 log.Debugw("no-more-data-blobs", log.Fields{"path": path, "name": name})
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400616 } else {
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400617 log.Debugw("cannot-process-field", log.Fields{
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400618 "type": pr.GetBranch().Node.Type,
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400619 "name": name,
620 })
621 }
622 }
623
624 return response
625}