blob: 297a74024bc705a14a735acfdedcbad16f8137b2 [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"
20 "crypto/md5"
21 "fmt"
22 "github.com/golang/protobuf/proto"
23 "github.com/opencord/voltha-go/common/log"
manikkaraj k9eb6cac2019-05-09 12:32:03 -040024 "github.com/opencord/voltha-go/db/kvstore"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040025 "reflect"
26 "sort"
27 "sync"
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -040028 "time"
Matt Jeanneretcab955f2019-04-10 15:45:57 -040029)
30
Matt Jeanneret384d8c92019-05-06 14:27:31 -040031// TODO: Cache logic will have to be revisited to cleanup unused entries in memory (disabled for now)
32//
manikkaraj k9eb6cac2019-05-09 12:32:03 -040033type revCacheSingleton struct {
34 sync.RWMutex
35 Cache sync.Map
36}
37
38var revCacheInstance *revCacheSingleton
39var revCacheOnce sync.Once
40
41func GetRevCache() *revCacheSingleton {
42 revCacheOnce.Do(func() {
43 revCacheInstance = &revCacheSingleton{Cache: sync.Map{}}
44 })
45 return revCacheInstance
46}
Matt Jeanneretcab955f2019-04-10 15:45:57 -040047
48type NonPersistedRevision struct {
Matt Jeanneret384d8c92019-05-06 14:27:31 -040049 mutex sync.RWMutex
50 Root *root
51 Config *DataRevision
52 childrenLock sync.RWMutex
53 Children map[string][]Revision
54 Hash string
55 Branch *Branch
56 WeakRef string
57 Name string
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -040058 lastUpdate time.Time
Matt Jeanneretcab955f2019-04-10 15:45:57 -040059}
60
61func NewNonPersistedRevision(root *root, branch *Branch, data interface{}, children map[string][]Revision) Revision {
62 r := &NonPersistedRevision{}
63 r.Root = root
64 r.Branch = branch
65 r.Config = NewDataRevision(root, data)
66 r.Children = children
67 r.Hash = r.hashContent()
68 return r
69}
70
71func (npr *NonPersistedRevision) SetConfig(config *DataRevision) {
72 npr.mutex.Lock()
73 defer npr.mutex.Unlock()
74 npr.Config = config
75}
76
77func (npr *NonPersistedRevision) GetConfig() *DataRevision {
78 npr.mutex.Lock()
79 defer npr.mutex.Unlock()
80 return npr.Config
81}
82
83func (npr *NonPersistedRevision) SetAllChildren(children map[string][]Revision) {
Matt Jeanneret384d8c92019-05-06 14:27:31 -040084 npr.childrenLock.Lock()
85 defer npr.childrenLock.Unlock()
86 npr.Children = make(map[string][]Revision)
Matt Jeanneretcab955f2019-04-10 15:45:57 -040087
Matt Jeanneret384d8c92019-05-06 14:27:31 -040088 for key, value := range children {
89 npr.Children[key] = make([]Revision, len(value))
90 copy(npr.Children[key], value)
Matt Jeanneretcab955f2019-04-10 15:45:57 -040091 }
92}
93
Matt Jeanneret384d8c92019-05-06 14:27:31 -040094func (npr *NonPersistedRevision) SetChildren(name string, children []Revision) {
95 npr.childrenLock.Lock()
96 defer npr.childrenLock.Unlock()
97
98 npr.Children[name] = make([]Revision, len(children))
99 copy(npr.Children[name], children)
100}
101
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400102func (npr *NonPersistedRevision) GetAllChildren() map[string][]Revision {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400103 npr.childrenLock.Lock()
104 defer npr.childrenLock.Unlock()
105
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400106 return npr.Children
107}
108
109func (npr *NonPersistedRevision) GetChildren(name string) []Revision {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400110 npr.childrenLock.Lock()
111 defer npr.childrenLock.Unlock()
112
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400113 if _, exists := npr.Children[name]; exists {
114 return npr.Children[name]
115 }
116 return nil
117}
118
119func (npr *NonPersistedRevision) SetHash(hash string) {
120 npr.mutex.Lock()
121 defer npr.mutex.Unlock()
122 npr.Hash = hash
123}
124
125func (npr *NonPersistedRevision) GetHash() string {
126 //npr.mutex.Lock()
127 //defer npr.mutex.Unlock()
128 return npr.Hash
129}
130
131func (npr *NonPersistedRevision) ClearHash() {
132 npr.mutex.Lock()
133 defer npr.mutex.Unlock()
134 npr.Hash = ""
135}
136
137func (npr *NonPersistedRevision) GetName() string {
138 //npr.mutex.Lock()
139 //defer npr.mutex.Unlock()
140 return npr.Name
141}
142
143func (npr *NonPersistedRevision) SetName(name string) {
144 //npr.mutex.Lock()
145 //defer npr.mutex.Unlock()
146 npr.Name = name
147}
148func (npr *NonPersistedRevision) SetBranch(branch *Branch) {
149 npr.mutex.Lock()
150 defer npr.mutex.Unlock()
151 npr.Branch = branch
152}
153
154func (npr *NonPersistedRevision) GetBranch() *Branch {
155 npr.mutex.Lock()
156 defer npr.mutex.Unlock()
157 return npr.Branch
158}
159
160func (npr *NonPersistedRevision) GetData() interface{} {
161 npr.mutex.Lock()
162 defer npr.mutex.Unlock()
163 if npr.Config == nil {
164 return nil
165 }
166 return npr.Config.Data
167}
168
169func (npr *NonPersistedRevision) GetNode() *node {
170 npr.mutex.Lock()
171 defer npr.mutex.Unlock()
172 return npr.Branch.Node
173}
174
175func (npr *NonPersistedRevision) Finalize(skipOnExist bool) {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400176 npr.Hash = npr.hashContent()
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400177}
178
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400179// hashContent generates a hash string based on the contents of the revision.
180// The string should be unique to avoid conflicts with other revisions
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400181func (npr *NonPersistedRevision) hashContent() string {
182 var buffer bytes.Buffer
183 var childrenKeys []string
184
185 if npr.Config != nil {
186 buffer.WriteString(npr.Config.Hash)
187 }
188
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400189 if npr.Name != "" {
190 buffer.WriteString(npr.Name)
191 }
192
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400193 for key := range npr.Children {
194 childrenKeys = append(childrenKeys, key)
195 }
196
197 sort.Strings(childrenKeys)
198
199 if len(npr.Children) > 0 {
200 // Loop through sorted Children keys
201 for _, key := range childrenKeys {
202 for _, child := range npr.Children[key] {
203 if child != nil && child.GetHash() != "" {
204 buffer.WriteString(child.GetHash())
205 }
206 }
207 }
208 }
209
210 return fmt.Sprintf("%x", md5.Sum(buffer.Bytes()))[:12]
211}
212
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400213// Get will retrieve the data for the current revision
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400214func (npr *NonPersistedRevision) Get(depth int) interface{} {
215 // 1. Clone the data to avoid any concurrent access issues
216 // 2. The current rev might still be pointing to an old config
217 // thus, force the revision to get its latest value
218 latestRev := npr.GetBranch().GetLatest()
219 originalData := proto.Clone(latestRev.GetData().(proto.Message))
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400220 data := originalData
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400221
222 if depth != 0 {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400223 // FIXME: Traversing the struct through reflection sometimes corrupts the data.
224 // Unlike the original python implementation, golang structs are not lazy loaded.
225 // Keeping this non-critical logic for now, but Get operations should be forced to
226 // depth=0 to avoid going through the following loop.
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400227 for fieldName, field := range ChildrenFields(latestRev.GetData()) {
228 childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
229 if field.IsContainer {
230 for _, rev := range latestRev.GetChildren(fieldName) {
231 childData := rev.Get(depth - 1)
232 foundEntry := false
233 for i := 0; i < childDataHolder.Len(); i++ {
234 cdh_if := childDataHolder.Index(i).Interface()
235 if cdh_if.(proto.Message).String() == childData.(proto.Message).String() {
236 foundEntry = true
237 break
238 }
239 }
240 if !foundEntry {
241 // avoid duplicates by adding it only if the child was not found in the holder
242 childDataHolder = reflect.Append(childDataHolder, reflect.ValueOf(childData))
243 }
244 }
245 } else {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400246 if revs := npr.GetBranch().GetLatest().GetChildren(fieldName); revs != nil && len(revs) > 0 {
247 rev := revs[0]
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400248 if rev != nil {
249 childData := rev.Get(depth - 1)
250 if reflect.TypeOf(childData) == reflect.TypeOf(childDataHolder.Interface()) {
251 childDataHolder = reflect.ValueOf(childData)
252 }
253 }
254 }
255 }
256 // Merge child data with cloned object
257 reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
258 }
259 }
260
261 result := data
262
263 if result != nil {
264 // We need to send back a copy of the retrieved object
265 result = proto.Clone(data.(proto.Message))
266 }
267
268 return result
269}
270
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400271// UpdateData will refresh the data content of the revision
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400272func (npr *NonPersistedRevision) UpdateData(data interface{}, branch *Branch) Revision {
273 npr.mutex.Lock()
274 defer npr.mutex.Unlock()
275
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400276 // Do not update the revision if data is the same
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400277 if npr.Config.Data != nil && npr.Config.hashData(npr.Root, data) == npr.Config.Hash {
278 log.Debugw("stored-data-matches-latest", log.Fields{"stored": npr.Config.Data, "provided": data})
279 return npr
280 }
281
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400282 // Construct a new revision based on the current one
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400283 newRev := NonPersistedRevision{}
284 newRev.Config = NewDataRevision(npr.Root, data)
285 newRev.Hash = npr.Hash
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400286 newRev.Root = npr.Root
287 newRev.Name = npr.Name
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400288 newRev.Branch = branch
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400289 newRev.lastUpdate = npr.lastUpdate
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400290
291 newRev.Children = make(map[string][]Revision)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400292 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400293 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
294 }
295
296 newRev.Finalize(false)
297
298 return &newRev
299}
300
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400301// UpdateChildren will refresh the list of children with the provided ones
302// It will carefully go through the list and ensure that no child is lost
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400303func (npr *NonPersistedRevision) UpdateChildren(name string, children []Revision, branch *Branch) Revision {
304 npr.mutex.Lock()
305 defer npr.mutex.Unlock()
306
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400307 // Construct a new revision based on the current one
308 updatedRev := &NonPersistedRevision{}
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400309 updatedRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
310 updatedRev.Hash = npr.Hash
311 updatedRev.Branch = branch
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400312 updatedRev.Name = npr.Name
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400313 updatedRev.lastUpdate = npr.lastUpdate
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400314
315 updatedRev.Children = make(map[string][]Revision)
316 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
317 updatedRev.Children[entryName] = append(updatedRev.Children[entryName], childrenEntry...)
318 }
319
320 var updatedChildren []Revision
321
322 // Verify if the map contains already contains an entry matching the name value
323 // If so, we need to retain the contents of that entry and merge them with the provided children revision list
324 if existingChildren := branch.GetLatest().GetChildren(name); existingChildren != nil {
325 // Construct a map of unique child names with the respective index value
326 // for the children in the existing revision as well as the new ones
327 existingNames := make(map[string]int)
328 newNames := make(map[string]int)
329
330 for i, newChild := range children {
331 newNames[newChild.GetName()] = i
332 }
333
334 for i, existingChild := range existingChildren {
335 existingNames[existingChild.GetName()] = i
336
337 // If an existing entry is not in the new list, add it to the updated list, so it is not forgotten
338 if _, exists := newNames[existingChild.GetName()]; !exists {
339 updatedChildren = append(updatedChildren, existingChild)
340 }
341 }
342
343 log.Debugw("existing-children-names", log.Fields{"hash": npr.GetHash(), "names": existingNames})
344
345 // Merge existing and new children
346 for _, newChild := range children {
347 nameIndex, nameExists := existingNames[newChild.GetName()]
348
349 // Does the existing list contain a child with that name?
350 if nameExists {
351 // Check if the data has changed or not
352 if existingChildren[nameIndex].GetData().(proto.Message).String() != newChild.GetData().(proto.Message).String() {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700353 log.Debugw("replacing-existing-child", log.Fields{
354 "old-hash": existingChildren[nameIndex].GetHash(),
355 "old-data": existingChildren[nameIndex].GetData(),
356 "new-hash": newChild.GetHash(),
357 "new-data": newChild.GetData(),
358 })
359
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400360 // replace entry
361 newChild.GetNode().Root = existingChildren[nameIndex].GetNode().Root
362 updatedChildren = append(updatedChildren, newChild)
363 } else {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700364 log.Debugw("keeping-existing-child", log.Fields{
365 "old-hash": existingChildren[nameIndex].GetHash(),
366 "old-data": existingChildren[nameIndex].GetData(),
367 "new-hash": newChild.GetHash(),
368 "new-data": newChild.GetData(),
369 })
370
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400371 // keep existing entry
372 updatedChildren = append(updatedChildren, existingChildren[nameIndex])
373 }
374 } else {
Mahir Gunyele77977b2019-06-27 05:36:22 -0700375 log.Debugw("adding-unknown-child", log.Fields{
376 "hash": newChild.GetHash(),
377 "data": newChild.GetData(),
378 })
379
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400380 // new entry ... just add it
381 updatedChildren = append(updatedChildren, newChild)
382 }
383 }
384
385 // Save children in new revision
386 updatedRev.SetChildren(name, updatedChildren)
387
388 updatedNames := make(map[string]int)
389 for i, updatedChild := range updatedChildren {
390 updatedNames[updatedChild.GetName()] = i
391 }
392
393 log.Debugw("updated-children-names", log.Fields{"hash": npr.GetHash(), "names": updatedNames})
394
395 } else {
396 // There are no children available, just save the provided ones
397 updatedRev.SetChildren(name, children)
398 }
399
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400400 updatedRev.Finalize(false)
401
402 return updatedRev
403}
404
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400405// UpdateAllChildren will replace the current list of children with the provided ones
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400406func (npr *NonPersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
407 npr.mutex.Lock()
408 defer npr.mutex.Unlock()
409
410 newRev := npr
411 newRev.Config = npr.Config
412 newRev.Hash = npr.Hash
413 newRev.Branch = branch
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400414 newRev.Name = npr.Name
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400415 newRev.lastUpdate = npr.lastUpdate
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400416
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400417 newRev.Children = make(map[string][]Revision)
418 for entryName, childrenEntry := range children {
419 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
420 }
421 newRev.Finalize(false)
422
423 return newRev
424}
425
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400426// Drop is used to indicate when a revision is no longer required
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400427func (npr *NonPersistedRevision) Drop(txid string, includeConfig bool) {
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400428 log.Debugw("dropping-revision", log.Fields{"hash": npr.GetHash(), "name": npr.GetName()})
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400429}
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400430
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400431// ChildDrop will remove a child entry matching the provided parameters from the current revision
432func (npr *NonPersistedRevision) ChildDrop(childType string, childHash string) {
433 if childType != "" {
434 children := make([]Revision, len(npr.GetChildren(childType)))
435 copy(children, npr.GetChildren(childType))
436 for i, child := range children {
437 if child.GetHash() == childHash {
438 children = append(children[:i], children[i+1:]...)
439 npr.SetChildren(childType, children)
440 break
441 }
442 }
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400443 }
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400444}
445
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400446func (npr *NonPersistedRevision) SetLastUpdate(ts ...time.Time) {
447 npr.mutex.Lock()
448 defer npr.mutex.Unlock()
449
450 if ts != nil && len(ts) > 0 {
451 npr.lastUpdate = ts[0]
452 } else {
453 npr.lastUpdate = time.Now()
454 }
455}
456
457func (npr *NonPersistedRevision) GetLastUpdate() time.Time {
458 npr.mutex.RLock()
459 defer npr.mutex.RUnlock()
460
461 return npr.lastUpdate
462}
463
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400464func (npr *NonPersistedRevision) LoadFromPersistence(path string, txid string, blobs map[string]*kvstore.KVPair) []Revision {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400465 // stub... required by interface
466 return nil
467}
468
469func (npr *NonPersistedRevision) SetupWatch(key string) {
470 // stub ... required by interface
471}
472
Mahir Gunyele77977b2019-06-27 05:36:22 -0700473func (npr *NonPersistedRevision) StorageDrop(txid string, includeConfig bool) {
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400474 // stub ... required by interface
475}