blob: 88320cb597d88cf92162c4c83cedb3e6f42d5850 [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 */
16package model
17
18import (
19 "bytes"
Manikkaraj kb1d51442019-07-23 10:41:02 -040020 "context"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040021 "crypto/md5"
22 "fmt"
23 "github.com/golang/protobuf/proto"
Scott Bakerf8424cc2019-10-18 11:26:50 -070024 "github.com/opencord/voltha-lib-go/pkg/db/kvstore"
25 "github.com/opencord/voltha-lib-go/pkg/log"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040026 "reflect"
27 "sort"
28 "sync"
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -040029 "time"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040030)
31
Matt Jeanneret384d8c92019-05-06 14:27:31 -040032// TODO: Cache logic will have to be revisited to cleanup unused entries in memory (disabled for now)
33//
manikkaraj k9eb6cac2019-05-09 12:32:03 -040034type revCacheSingleton struct {
35 sync.RWMutex
36 Cache sync.Map
37}
38
Manikkaraj kb1d51442019-07-23 10:41:02 -040039func (s *revCacheSingleton) Get(path string) (interface{}, bool) {
40 return s.Cache.Load(path)
41}
42func (s *revCacheSingleton) Set(path string, value interface{}) {
43 s.Cache.Store(path, value)
44}
45func (s *revCacheSingleton) Delete(path string) {
46 s.Cache.Delete(path)
47}
48
manikkaraj k9eb6cac2019-05-09 12:32:03 -040049var revCacheInstance *revCacheSingleton
50var revCacheOnce sync.Once
51
52func GetRevCache() *revCacheSingleton {
53 revCacheOnce.Do(func() {
54 revCacheInstance = &revCacheSingleton{Cache: sync.Map{}}
55 })
56 return revCacheInstance
57}
Matt Jeanneretcab955f2019-04-10 15:45:57 -040058
59type NonPersistedRevision struct {
Matt Jeanneret384d8c92019-05-06 14:27:31 -040060 mutex sync.RWMutex
61 Root *root
62 Config *DataRevision
63 childrenLock sync.RWMutex
64 Children map[string][]Revision
65 Hash string
66 Branch *Branch
67 WeakRef string
68 Name string
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -040069 lastUpdate time.Time
Matt Jeanneretcab955f2019-04-10 15:45:57 -040070}
71
72func NewNonPersistedRevision(root *root, branch *Branch, data interface{}, children map[string][]Revision) Revision {
73 r := &NonPersistedRevision{}
74 r.Root = root
75 r.Branch = branch
76 r.Config = NewDataRevision(root, data)
77 r.Children = children
78 r.Hash = r.hashContent()
79 return r
80}
81
82func (npr *NonPersistedRevision) SetConfig(config *DataRevision) {
83 npr.mutex.Lock()
84 defer npr.mutex.Unlock()
85 npr.Config = config
86}
87
88func (npr *NonPersistedRevision) GetConfig() *DataRevision {
89 npr.mutex.Lock()
90 defer npr.mutex.Unlock()
91 return npr.Config
92}
93
94func (npr *NonPersistedRevision) SetAllChildren(children map[string][]Revision) {
Matt Jeanneret384d8c92019-05-06 14:27:31 -040095 npr.childrenLock.Lock()
96 defer npr.childrenLock.Unlock()
97 npr.Children = make(map[string][]Revision)
Matt Jeanneretcab955f2019-04-10 15:45:57 -040098
Matt Jeanneret384d8c92019-05-06 14:27:31 -040099 for key, value := range children {
100 npr.Children[key] = make([]Revision, len(value))
101 copy(npr.Children[key], value)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400102 }
103}
104
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400105func (npr *NonPersistedRevision) SetChildren(name string, children []Revision) {
106 npr.childrenLock.Lock()
107 defer npr.childrenLock.Unlock()
108
109 npr.Children[name] = make([]Revision, len(children))
110 copy(npr.Children[name], children)
111}
112
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400113func (npr *NonPersistedRevision) GetAllChildren() map[string][]Revision {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400114 npr.childrenLock.Lock()
115 defer npr.childrenLock.Unlock()
116
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400117 return npr.Children
118}
119
120func (npr *NonPersistedRevision) GetChildren(name string) []Revision {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400121 npr.childrenLock.Lock()
122 defer npr.childrenLock.Unlock()
123
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400124 if _, exists := npr.Children[name]; exists {
125 return npr.Children[name]
126 }
127 return nil
128}
129
130func (npr *NonPersistedRevision) SetHash(hash string) {
131 npr.mutex.Lock()
132 defer npr.mutex.Unlock()
133 npr.Hash = hash
134}
135
136func (npr *NonPersistedRevision) GetHash() string {
137 //npr.mutex.Lock()
138 //defer npr.mutex.Unlock()
139 return npr.Hash
140}
141
142func (npr *NonPersistedRevision) ClearHash() {
143 npr.mutex.Lock()
144 defer npr.mutex.Unlock()
145 npr.Hash = ""
146}
147
148func (npr *NonPersistedRevision) GetName() string {
149 //npr.mutex.Lock()
150 //defer npr.mutex.Unlock()
151 return npr.Name
152}
153
154func (npr *NonPersistedRevision) SetName(name string) {
155 //npr.mutex.Lock()
156 //defer npr.mutex.Unlock()
157 npr.Name = name
158}
159func (npr *NonPersistedRevision) SetBranch(branch *Branch) {
160 npr.mutex.Lock()
161 defer npr.mutex.Unlock()
162 npr.Branch = branch
163}
164
165func (npr *NonPersistedRevision) GetBranch() *Branch {
166 npr.mutex.Lock()
167 defer npr.mutex.Unlock()
168 return npr.Branch
169}
170
171func (npr *NonPersistedRevision) GetData() interface{} {
172 npr.mutex.Lock()
173 defer npr.mutex.Unlock()
174 if npr.Config == nil {
175 return nil
176 }
177 return npr.Config.Data
178}
179
180func (npr *NonPersistedRevision) GetNode() *node {
181 npr.mutex.Lock()
182 defer npr.mutex.Unlock()
183 return npr.Branch.Node
184}
185
186func (npr *NonPersistedRevision) Finalize(skipOnExist bool) {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400187 npr.Hash = npr.hashContent()
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400188}
189
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400190// hashContent generates a hash string based on the contents of the revision.
191// The string should be unique to avoid conflicts with other revisions
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400192func (npr *NonPersistedRevision) hashContent() string {
193 var buffer bytes.Buffer
194 var childrenKeys []string
195
196 if npr.Config != nil {
197 buffer.WriteString(npr.Config.Hash)
198 }
199
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400200 if npr.Name != "" {
201 buffer.WriteString(npr.Name)
202 }
203
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400204 for key := range npr.Children {
205 childrenKeys = append(childrenKeys, key)
206 }
207
208 sort.Strings(childrenKeys)
209
210 if len(npr.Children) > 0 {
211 // Loop through sorted Children keys
212 for _, key := range childrenKeys {
213 for _, child := range npr.Children[key] {
214 if child != nil && child.GetHash() != "" {
215 buffer.WriteString(child.GetHash())
216 }
217 }
218 }
219 }
220
221 return fmt.Sprintf("%x", md5.Sum(buffer.Bytes()))[:12]
222}
223
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400224// Get will retrieve the data for the current revision
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400225func (npr *NonPersistedRevision) Get(depth int) interface{} {
226 // 1. Clone the data to avoid any concurrent access issues
227 // 2. The current rev might still be pointing to an old config
228 // thus, force the revision to get its latest value
229 latestRev := npr.GetBranch().GetLatest()
230 originalData := proto.Clone(latestRev.GetData().(proto.Message))
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400231 data := originalData
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400232
233 if depth != 0 {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400234 // FIXME: Traversing the struct through reflection sometimes corrupts the data.
235 // Unlike the original python implementation, golang structs are not lazy loaded.
236 // Keeping this non-critical logic for now, but Get operations should be forced to
237 // depth=0 to avoid going through the following loop.
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400238 for fieldName, field := range ChildrenFields(latestRev.GetData()) {
239 childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
240 if field.IsContainer {
241 for _, rev := range latestRev.GetChildren(fieldName) {
242 childData := rev.Get(depth - 1)
243 foundEntry := false
244 for i := 0; i < childDataHolder.Len(); i++ {
245 cdh_if := childDataHolder.Index(i).Interface()
246 if cdh_if.(proto.Message).String() == childData.(proto.Message).String() {
247 foundEntry = true
248 break
249 }
250 }
251 if !foundEntry {
252 // avoid duplicates by adding it only if the child was not found in the holder
253 childDataHolder = reflect.Append(childDataHolder, reflect.ValueOf(childData))
254 }
255 }
256 } else {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400257 if revs := npr.GetBranch().GetLatest().GetChildren(fieldName); revs != nil && len(revs) > 0 {
258 rev := revs[0]
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400259 if rev != nil {
260 childData := rev.Get(depth - 1)
261 if reflect.TypeOf(childData) == reflect.TypeOf(childDataHolder.Interface()) {
262 childDataHolder = reflect.ValueOf(childData)
263 }
264 }
265 }
266 }
267 // Merge child data with cloned object
268 reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
269 }
270 }
271
272 result := data
273
274 if result != nil {
275 // We need to send back a copy of the retrieved object
276 result = proto.Clone(data.(proto.Message))
277 }
278
279 return result
280}
281
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400282// UpdateData will refresh the data content of the revision
Manikkaraj kb1d51442019-07-23 10:41:02 -0400283func (npr *NonPersistedRevision) UpdateData(ctx context.Context, data interface{}, branch *Branch) Revision {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400284 npr.mutex.Lock()
285 defer npr.mutex.Unlock()
286
Manikkaraj kb1d51442019-07-23 10:41:02 -0400287 if ctx != nil {
288 if ctxTS, ok := ctx.Value(RequestTimestamp).(int64); ok && npr.lastUpdate.UnixNano() > ctxTS {
289 log.Warnw("data-is-older-than-current", log.Fields{"ctx-ts": ctxTS, "rev-ts": npr.lastUpdate.UnixNano()})
290 return npr
291 }
292 }
293
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400294 // Do not update the revision if data is the same
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400295 if npr.Config.Data != nil && npr.Config.hashData(npr.Root, data) == npr.Config.Hash {
296 log.Debugw("stored-data-matches-latest", log.Fields{"stored": npr.Config.Data, "provided": data})
297 return npr
298 }
299
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400300 // Construct a new revision based on the current one
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400301 newRev := NonPersistedRevision{}
302 newRev.Config = NewDataRevision(npr.Root, data)
303 newRev.Hash = npr.Hash
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400304 newRev.Root = npr.Root
305 newRev.Name = npr.Name
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400306 newRev.Branch = branch
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400307 newRev.lastUpdate = npr.lastUpdate
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400308
309 newRev.Children = make(map[string][]Revision)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400310 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400311 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
312 }
313
314 newRev.Finalize(false)
315
316 return &newRev
317}
318
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400319// UpdateChildren will refresh the list of children with the provided ones
320// It will carefully go through the list and ensure that no child is lost
Manikkaraj kb1d51442019-07-23 10:41:02 -0400321func (npr *NonPersistedRevision) UpdateChildren(ctx context.Context, name string, children []Revision, branch *Branch) Revision {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400322 npr.mutex.Lock()
323 defer npr.mutex.Unlock()
324
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400325 // Construct a new revision based on the current one
326 updatedRev := &NonPersistedRevision{}
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400327 updatedRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
328 updatedRev.Hash = npr.Hash
329 updatedRev.Branch = branch
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400330 updatedRev.Name = npr.Name
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400331 updatedRev.lastUpdate = npr.lastUpdate
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400332
333 updatedRev.Children = make(map[string][]Revision)
334 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
335 updatedRev.Children[entryName] = append(updatedRev.Children[entryName], childrenEntry...)
336 }
337
338 var updatedChildren []Revision
339
340 // Verify if the map contains already contains an entry matching the name value
341 // If so, we need to retain the contents of that entry and merge them with the provided children revision list
342 if existingChildren := branch.GetLatest().GetChildren(name); existingChildren != nil {
343 // Construct a map of unique child names with the respective index value
344 // for the children in the existing revision as well as the new ones
345 existingNames := make(map[string]int)
346 newNames := make(map[string]int)
347
348 for i, newChild := range children {
349 newNames[newChild.GetName()] = i
350 }
351
352 for i, existingChild := range existingChildren {
353 existingNames[existingChild.GetName()] = i
354
355 // If an existing entry is not in the new list, add it to the updated list, so it is not forgotten
356 if _, exists := newNames[existingChild.GetName()]; !exists {
357 updatedChildren = append(updatedChildren, existingChild)
358 }
359 }
360
361 log.Debugw("existing-children-names", log.Fields{"hash": npr.GetHash(), "names": existingNames})
362
363 // Merge existing and new children
364 for _, newChild := range children {
365 nameIndex, nameExists := existingNames[newChild.GetName()]
366
367 // Does the existing list contain a child with that name?
368 if nameExists {
369 // Check if the data has changed or not
370 if existingChildren[nameIndex].GetData().(proto.Message).String() != newChild.GetData().(proto.Message).String() {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700371 log.Debugw("replacing-existing-child", log.Fields{
372 "old-hash": existingChildren[nameIndex].GetHash(),
373 "old-data": existingChildren[nameIndex].GetData(),
374 "new-hash": newChild.GetHash(),
375 "new-data": newChild.GetData(),
376 })
377
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400378 // replace entry
Manikkaraj kb1d51442019-07-23 10:41:02 -0400379 newChild.GetNode().SetRoot(existingChildren[nameIndex].GetNode().GetRoot())
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400380 updatedChildren = append(updatedChildren, newChild)
381 } else {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700382 log.Debugw("keeping-existing-child", log.Fields{
383 "old-hash": existingChildren[nameIndex].GetHash(),
384 "old-data": existingChildren[nameIndex].GetData(),
385 "new-hash": newChild.GetHash(),
386 "new-data": newChild.GetData(),
387 })
388
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400389 // keep existing entry
390 updatedChildren = append(updatedChildren, existingChildren[nameIndex])
391 }
392 } else {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700393 log.Debugw("adding-unknown-child", log.Fields{
394 "hash": newChild.GetHash(),
395 "data": newChild.GetData(),
396 })
397
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400398 // new entry ... just add it
399 updatedChildren = append(updatedChildren, newChild)
400 }
401 }
402
403 // Save children in new revision
404 updatedRev.SetChildren(name, updatedChildren)
405
406 updatedNames := make(map[string]int)
407 for i, updatedChild := range updatedChildren {
408 updatedNames[updatedChild.GetName()] = i
409 }
410
411 log.Debugw("updated-children-names", log.Fields{"hash": npr.GetHash(), "names": updatedNames})
412
413 } else {
414 // There are no children available, just save the provided ones
415 updatedRev.SetChildren(name, children)
416 }
417
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400418 updatedRev.Finalize(false)
419
420 return updatedRev
421}
422
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400423// UpdateAllChildren will replace the current list of children with the provided ones
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400424func (npr *NonPersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
425 npr.mutex.Lock()
426 defer npr.mutex.Unlock()
427
428 newRev := npr
429 newRev.Config = npr.Config
430 newRev.Hash = npr.Hash
431 newRev.Branch = branch
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400432 newRev.Name = npr.Name
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400433 newRev.lastUpdate = npr.lastUpdate
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400434
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400435 newRev.Children = make(map[string][]Revision)
436 for entryName, childrenEntry := range children {
437 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
438 }
439 newRev.Finalize(false)
440
441 return newRev
442}
443
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400444// Drop is used to indicate when a revision is no longer required
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400445func (npr *NonPersistedRevision) Drop(txid string, includeConfig bool) {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400446 log.Debugw("dropping-revision", log.Fields{"hash": npr.GetHash(), "name": npr.GetName()})
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400447}
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400448
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400449// ChildDrop will remove a child entry matching the provided parameters from the current revision
450func (npr *NonPersistedRevision) ChildDrop(childType string, childHash string) {
451 if childType != "" {
452 children := make([]Revision, len(npr.GetChildren(childType)))
453 copy(children, npr.GetChildren(childType))
454 for i, child := range children {
455 if child.GetHash() == childHash {
456 children = append(children[:i], children[i+1:]...)
457 npr.SetChildren(childType, children)
458 break
459 }
460 }
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400461 }
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400462}
463
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400464func (npr *NonPersistedRevision) SetLastUpdate(ts ...time.Time) {
465 npr.mutex.Lock()
466 defer npr.mutex.Unlock()
467
468 if ts != nil && len(ts) > 0 {
469 npr.lastUpdate = ts[0]
470 } else {
471 npr.lastUpdate = time.Now()
472 }
473}
474
475func (npr *NonPersistedRevision) GetLastUpdate() time.Time {
476 npr.mutex.RLock()
477 defer npr.mutex.RUnlock()
478
479 return npr.lastUpdate
480}
481
Manikkaraj kb1d51442019-07-23 10:41:02 -0400482func (npr *NonPersistedRevision) LoadFromPersistence(ctx context.Context, path string, txid string, blobs map[string]*kvstore.KVPair) []Revision {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400483 // stub... required by interface
484 return nil
485}
486
487func (npr *NonPersistedRevision) SetupWatch(key string) {
488 // stub ... required by interface
489}
490
Mahir Gunyele77977b2019-06-27 05:36:22 -0700491func (npr *NonPersistedRevision) StorageDrop(txid string, includeConfig bool) {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400492 // stub ... required by interface
493}
Manikkaraj kb1d51442019-07-23 10:41:02 -0400494
495func (npr *NonPersistedRevision) getVersion() int64 {
496 return -1
497}