blob: 0ccc58eda0ca25ecc30c94a4c9e926ee2d4fe1b4 [file] [log] [blame]
Stephane Barbarieec0919b2018-09-05 14:14:29 -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"
Stephane Barbarie126101e2018-10-11 16:18:48 -040022 "github.com/golang/protobuf/proto"
Stephane Barbarie933b09b2019-01-09 11:12:09 -050023 "github.com/opencord/voltha-go/common/log"
Stephane Barbarieec0919b2018-09-05 14:14:29 -040024 "reflect"
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040025 "runtime/debug"
Stephane Barbarieec0919b2018-09-05 14:14:29 -040026 "sort"
Stephane Barbariedc5022d2018-11-19 15:21:44 -050027 "sync"
Stephane Barbarieec0919b2018-09-05 14:14:29 -040028)
29
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040030// TODO: Cache logic will have to be revisited to cleanup unused entries in memory (disabled for now)
31//
32//type revCacheSingleton struct {
33// sync.RWMutex
34// //Cache map[string]interface{}
35// Cache sync.Map
36//}
37//
38//var revCacheInstance *revCacheSingleton
39//var revCacheOnce sync.Once
40//
41//func GetRevCache() *revCacheSingleton {
42// revCacheOnce.Do(func() {
43// //revCacheInstance = &revCacheSingleton{Cache: make(map[string]interface{})}
44// revCacheInstance = &revCacheSingleton{Cache: sync.Map{}}
45// })
46// return revCacheInstance
47//}
Stephane Barbarieec0919b2018-09-05 14:14:29 -040048
49type NonPersistedRevision struct {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040050 mutex sync.RWMutex
51 Root *root
52 Config *DataRevision
53 childrenLock sync.RWMutex
54 Children map[string][]Revision
55 Hash string
56 Branch *Branch
57 WeakRef string
58 Name string
59 discarded bool
Stephane Barbarieec0919b2018-09-05 14:14:29 -040060}
61
Stephane Barbariedc5022d2018-11-19 15:21:44 -050062func NewNonPersistedRevision(root *root, branch *Branch, data interface{}, children map[string][]Revision) Revision {
63 r := &NonPersistedRevision{}
64 r.Root = root
65 r.Branch = branch
66 r.Config = NewDataRevision(root, data)
67 r.Children = children
Stephane Barbarief7fc1782019-03-28 22:33:41 -040068 r.Hash = r.hashContent()
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040069 r.discarded = false
Stephane Barbariedc5022d2018-11-19 15:21:44 -050070 return r
Stephane Barbarieec0919b2018-09-05 14:14:29 -040071}
72
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040073func (npr *NonPersistedRevision) IsDiscarded() bool {
74 return npr.discarded
75}
76
Stephane Barbarieec0919b2018-09-05 14:14:29 -040077func (npr *NonPersistedRevision) SetConfig(config *DataRevision) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050078 npr.mutex.Lock()
79 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -040080 npr.Config = config
81}
82
83func (npr *NonPersistedRevision) GetConfig() *DataRevision {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050084 npr.mutex.Lock()
85 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -040086 return npr.Config
87}
88
Stephane Barbarie3cb01222019-01-16 17:15:56 -050089func (npr *NonPersistedRevision) SetAllChildren(children map[string][]Revision) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040090 npr.childrenLock.Lock()
91 defer npr.childrenLock.Unlock()
92 npr.Children = make(map[string][]Revision)
Stephane Barbarieec0919b2018-09-05 14:14:29 -040093
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040094 for key, value := range children {
95 npr.Children[key] = make([]Revision, len(value))
96 copy(npr.Children[key], value)
Stephane Barbarie3cb01222019-01-16 17:15:56 -050097 }
98}
99
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400100func (npr *NonPersistedRevision) SetChildren(name string, children []Revision) {
101 npr.childrenLock.Lock()
102 defer npr.childrenLock.Unlock()
103
104 npr.Children[name] = make([]Revision, len(children))
105 copy(npr.Children[name], children)
106}
107
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500108func (npr *NonPersistedRevision) GetAllChildren() map[string][]Revision {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400109 npr.childrenLock.Lock()
110 defer npr.childrenLock.Unlock()
111
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400112 return npr.Children
113}
114
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500115func (npr *NonPersistedRevision) GetChildren(name string) []Revision {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400116 npr.childrenLock.Lock()
117 defer npr.childrenLock.Unlock()
118
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500119 if _, exists := npr.Children[name]; exists {
120 return npr.Children[name]
121 }
122 return nil
123}
124
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400125func (npr *NonPersistedRevision) SetHash(hash string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500126 npr.mutex.Lock()
127 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400128 npr.Hash = hash
129}
130
131func (npr *NonPersistedRevision) GetHash() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500132 //npr.mutex.Lock()
133 //defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400134 return npr.Hash
135}
136
137func (npr *NonPersistedRevision) ClearHash() {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500138 npr.mutex.Lock()
139 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400140 npr.Hash = ""
141}
142
Stephane Barbarief7fc1782019-03-28 22:33:41 -0400143func (npr *NonPersistedRevision) GetName() string {
144 //npr.mutex.Lock()
145 //defer npr.mutex.Unlock()
146 return npr.Name
147}
148
149func (npr *NonPersistedRevision) SetName(name string) {
150 //npr.mutex.Lock()
151 //defer npr.mutex.Unlock()
152 npr.Name = name
153}
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400154func (npr *NonPersistedRevision) SetBranch(branch *Branch) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500155 npr.mutex.Lock()
156 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400157 npr.Branch = branch
158}
159
160func (npr *NonPersistedRevision) GetBranch() *Branch {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500161 npr.mutex.Lock()
162 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400163 return npr.Branch
164}
165
166func (npr *NonPersistedRevision) GetData() interface{} {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500167 npr.mutex.Lock()
168 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400169 if npr.Config == nil {
170 return nil
171 }
172 return npr.Config.Data
173}
174
Stephane Barbarie06c4a742018-10-01 11:09:32 -0400175func (npr *NonPersistedRevision) GetNode() *node {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500176 npr.mutex.Lock()
177 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400178 return npr.Branch.Node
179}
180
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500181func (npr *NonPersistedRevision) Finalize(skipOnExist bool) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400182 npr.Hash = npr.hashContent()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400183}
184
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400185// hashContent generates a hash string based on the contents of the revision.
186// The string should be unique to avoid conflicts with other revisions
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400187func (npr *NonPersistedRevision) hashContent() string {
188 var buffer bytes.Buffer
189 var childrenKeys []string
190
191 if npr.Config != nil {
192 buffer.WriteString(npr.Config.Hash)
193 }
194
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400195 if npr.Name != "" {
196 buffer.WriteString(npr.Name)
197 }
198
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500199 for key := range npr.Children {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400200 childrenKeys = append(childrenKeys, key)
201 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500202
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400203 sort.Strings(childrenKeys)
204
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500205 if len(npr.Children) > 0 {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400206 // Loop through sorted Children keys
207 for _, key := range childrenKeys {
208 for _, child := range npr.Children[key] {
209 if child != nil && child.GetHash() != "" {
210 buffer.WriteString(child.GetHash())
211 }
212 }
213 }
214 }
215
216 return fmt.Sprintf("%x", md5.Sum(buffer.Bytes()))[:12]
217}
218
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400219// Get will retrieve the data for the current revision
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400220func (npr *NonPersistedRevision) Get(depth int) interface{} {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500221 // 1. Clone the data to avoid any concurrent access issues
222 // 2. The current rev might still be pointing to an old config
223 // thus, force the revision to get its latest value
224 latestRev := npr.GetBranch().GetLatest()
225 originalData := proto.Clone(latestRev.GetData().(proto.Message))
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500226 data := originalData
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400227
228 if depth != 0 {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400229 // FIXME: Traversing the struct through reflection sometimes corrupts the data.
230 // Unlike the original python implementation, golang structs are not lazy loaded.
231 // Keeping this non-critical logic for now, but Get operations should be forced to
232 // depth=0 to avoid going through the following loop.
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500233 for fieldName, field := range ChildrenFields(latestRev.GetData()) {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400234 childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
235 if field.IsContainer {
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500236 for _, rev := range latestRev.GetChildren(fieldName) {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400237 childData := rev.Get(depth - 1)
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400238 foundEntry := false
239 for i := 0; i < childDataHolder.Len(); i++ {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500240 cdh_if := childDataHolder.Index(i).Interface()
241 if cdh_if.(proto.Message).String() == childData.(proto.Message).String() {
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400242 foundEntry = true
243 break
244 }
245 }
246 if !foundEntry {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500247 // avoid duplicates by adding it only if the child was not found in the holder
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400248 childDataHolder = reflect.Append(childDataHolder, reflect.ValueOf(childData))
249 }
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400250 }
251 } else {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400252 if revs := npr.GetBranch().GetLatest().GetChildren(fieldName); revs != nil && len(revs) > 0 {
253 rev := revs[0]
Stephane Barbarie126101e2018-10-11 16:18:48 -0400254 if rev != nil {
255 childData := rev.Get(depth - 1)
256 if reflect.TypeOf(childData) == reflect.TypeOf(childDataHolder.Interface()) {
257 childDataHolder = reflect.ValueOf(childData)
258 }
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400259 }
260 }
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400261 }
262 // Merge child data with cloned object
263 reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
264 }
265 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400266
267 result := data
268
269 if result != nil {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500270 // We need to send back a copy of the retrieved object
271 result = proto.Clone(data.(proto.Message))
Stephane Barbarie126101e2018-10-11 16:18:48 -0400272 }
273
274 return result
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400275}
276
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400277// UpdateData will refresh the data content of the revision
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400278func (npr *NonPersistedRevision) UpdateData(data interface{}, branch *Branch) Revision {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500279 npr.mutex.Lock()
280 defer npr.mutex.Unlock()
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400281
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400282 // Do not update the revision if data is the same
Stephane Barbariedf5479f2019-01-29 22:13:00 -0500283 if npr.Config.Data != nil && npr.Config.hashData(npr.Root, data) == npr.Config.Hash {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500284 log.Debugw("stored-data-matches-latest", log.Fields{"stored": npr.Config.Data, "provided": data})
Stephane Barbariedf5479f2019-01-29 22:13:00 -0500285 return npr
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500286 }
287
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400288 // Construct a new revision based on the current one
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500289 newRev := NonPersistedRevision{}
290 newRev.Config = NewDataRevision(npr.Root, data)
291 newRev.Hash = npr.Hash
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400292 newRev.Root = npr.Root
293 newRev.Name = npr.Name
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500294 newRev.Branch = branch
295
296 newRev.Children = make(map[string][]Revision)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400297 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500298 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
299 }
300
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500301 newRev.Finalize(false)
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400302
303 return &newRev
304}
305
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400306// UpdateChildren will refresh the list of children with the provided ones
307// It will carefully go through the list and ensure that no child is lost
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400308func (npr *NonPersistedRevision) UpdateChildren(name string, children []Revision, branch *Branch) Revision {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500309 npr.mutex.Lock()
310 defer npr.mutex.Unlock()
311
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400312 // Construct a new revision based on the current one
313 updatedRev := &NonPersistedRevision{}
Stephane Barbarie933b09b2019-01-09 11:12:09 -0500314 updatedRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
315 updatedRev.Hash = npr.Hash
316 updatedRev.Branch = branch
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400317 updatedRev.Name = npr.Name
318
319 updatedRev.Children = make(map[string][]Revision)
320 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
321 updatedRev.Children[entryName] = append(updatedRev.Children[entryName], childrenEntry...)
322 }
323
324 var updatedChildren []Revision
325
326 // Verify if the map contains already contains an entry matching the name value
327 // If so, we need to retain the contents of that entry and merge them with the provided children revision list
328 if existingChildren := branch.GetLatest().GetChildren(name); existingChildren != nil {
329 // Construct a map of unique child names with the respective index value
330 // for the children in the existing revision as well as the new ones
331 existingNames := make(map[string]int)
332 newNames := make(map[string]int)
333
334 for i, newChild := range children {
335 newNames[newChild.GetName()] = i
336 }
337
338 for i, existingChild := range existingChildren {
339 existingNames[existingChild.GetName()] = i
340
341 // If an existing entry is not in the new list, add it to the updated list, so it is not forgotten
342 if _, exists := newNames[existingChild.GetName()]; !exists {
343 updatedChildren = append(updatedChildren, existingChild)
344 }
345 }
346
347 log.Debugw("existing-children-names", log.Fields{"hash": npr.GetHash(), "names": existingNames})
348
349 // Merge existing and new children
350 for _, newChild := range children {
351 nameIndex, nameExists := existingNames[newChild.GetName()]
352
353 // Does the existing list contain a child with that name?
354 if nameExists {
355 // Check if the data has changed or not
356 if existingChildren[nameIndex].GetData().(proto.Message).String() != newChild.GetData().(proto.Message).String() {
357 // replace entry
358 newChild.GetNode().Root = existingChildren[nameIndex].GetNode().Root
359 updatedChildren = append(updatedChildren, newChild)
360 } else {
361 // keep existing entry
362 updatedChildren = append(updatedChildren, existingChildren[nameIndex])
363 }
364 } else {
365 // new entry ... just add it
366 updatedChildren = append(updatedChildren, newChild)
367 }
368 }
369
370 // Save children in new revision
371 updatedRev.SetChildren(name, updatedChildren)
372
373 updatedNames := make(map[string]int)
374 for i, updatedChild := range updatedChildren {
375 updatedNames[updatedChild.GetName()] = i
376 }
377
378 log.Debugw("updated-children-names", log.Fields{"hash": npr.GetHash(), "names": updatedNames})
379
380 } else {
381 // There are no children available, just save the provided ones
382 updatedRev.SetChildren(name, children)
383 }
384
Stephane Barbarie933b09b2019-01-09 11:12:09 -0500385 updatedRev.Finalize(false)
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400386
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500387 return updatedRev
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400388}
389
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400390// UpdateAllChildren will replace the current list of children with the provided ones
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400391func (npr *NonPersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500392 npr.mutex.Lock()
393 defer npr.mutex.Unlock()
394
Stephane Barbariec53a2752019-03-08 17:50:10 -0500395 newRev := npr
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500396 newRev.Config = npr.Config
397 newRev.Hash = npr.Hash
398 newRev.Branch = branch
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400399 newRev.Name = npr.Name
400
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500401 newRev.Children = make(map[string][]Revision)
Stephane Barbariec53a2752019-03-08 17:50:10 -0500402 for entryName, childrenEntry := range children {
403 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500404 }
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500405 newRev.Finalize(false)
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400406
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500407 return newRev
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400408}
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400409
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400410// Drop is used to indicate when a revision is no longer required
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400411func (npr *NonPersistedRevision) Drop(txid string, includeConfig bool) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400412 log.Debugw("dropping-revision", log.Fields{"hash": npr.GetHash(), "stack": string(debug.Stack())})
413 npr.discarded = true
414}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500415
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400416// ChildDrop will remove a child entry matching the provided parameters from the current revision
417func (npr *NonPersistedRevision) ChildDrop(childType string, childHash string) {
418 if childType != "" {
419 children := make([]Revision, len(npr.GetChildren(childType)))
420 copy(children, npr.GetChildren(childType))
421 for i, child := range children {
422 if child.GetHash() == childHash {
423 children = append(children[:i], children[i+1:]...)
424 npr.SetChildren(childType, children)
425 break
426 }
427 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500428 }
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400429}
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500430
431func (npr *NonPersistedRevision) LoadFromPersistence(path string, txid string) []Revision {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500432 // stub... required by interface
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500433 return nil
434}
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500435
436func (npr *NonPersistedRevision) SetupWatch(key string) {
437 // stub ... required by interface
Stephane Barbarief7fc1782019-03-28 22:33:41 -0400438}
439
440func (pr *NonPersistedRevision) StorageDrop(txid string, includeConfig bool) {
441 // stub ... required by interface
442}