blob: d7b0b584be90aff754f3b6a7f69c60a2c3355f3c [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 Barbarie7512fc82019-05-07 12:25:46 -040024 "github.com/opencord/voltha-go/db/kvstore"
Stephane Barbarieec0919b2018-09-05 14:14:29 -040025 "reflect"
26 "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//
Stephane Barbarie7512fc82019-05-07 12:25:46 -040032type revCacheSingleton struct {
33 sync.RWMutex
34 Cache sync.Map
35}
36
37var revCacheInstance *revCacheSingleton
38var revCacheOnce sync.Once
39
40func GetRevCache() *revCacheSingleton {
41 revCacheOnce.Do(func() {
42 revCacheInstance = &revCacheSingleton{Cache: sync.Map{}}
43 })
44 return revCacheInstance
45}
Stephane Barbarieec0919b2018-09-05 14:14:29 -040046
47type NonPersistedRevision struct {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040048 mutex sync.RWMutex
49 Root *root
50 Config *DataRevision
51 childrenLock sync.RWMutex
52 Children map[string][]Revision
53 Hash string
54 Branch *Branch
55 WeakRef string
56 Name string
57 discarded bool
Stephane Barbarieec0919b2018-09-05 14:14:29 -040058}
59
Stephane Barbariedc5022d2018-11-19 15:21:44 -050060func NewNonPersistedRevision(root *root, branch *Branch, data interface{}, children map[string][]Revision) Revision {
61 r := &NonPersistedRevision{}
62 r.Root = root
63 r.Branch = branch
64 r.Config = NewDataRevision(root, data)
65 r.Children = children
Stephane Barbarief7fc1782019-03-28 22:33:41 -040066 r.Hash = r.hashContent()
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040067 r.discarded = false
Stephane Barbariedc5022d2018-11-19 15:21:44 -050068 return r
Stephane Barbarieec0919b2018-09-05 14:14:29 -040069}
70
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040071func (npr *NonPersistedRevision) IsDiscarded() bool {
72 return npr.discarded
73}
74
Stephane Barbarieec0919b2018-09-05 14:14:29 -040075func (npr *NonPersistedRevision) SetConfig(config *DataRevision) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050076 npr.mutex.Lock()
77 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -040078 npr.Config = config
79}
80
81func (npr *NonPersistedRevision) GetConfig() *DataRevision {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050082 npr.mutex.Lock()
83 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -040084 return npr.Config
85}
86
Stephane Barbarie3cb01222019-01-16 17:15:56 -050087func (npr *NonPersistedRevision) SetAllChildren(children map[string][]Revision) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040088 npr.childrenLock.Lock()
89 defer npr.childrenLock.Unlock()
90 npr.Children = make(map[string][]Revision)
Stephane Barbarieec0919b2018-09-05 14:14:29 -040091
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040092 for key, value := range children {
93 npr.Children[key] = make([]Revision, len(value))
94 copy(npr.Children[key], value)
Stephane Barbarie3cb01222019-01-16 17:15:56 -050095 }
96}
97
Stephane Barbarie40fd3b22019-04-23 21:50:47 -040098func (npr *NonPersistedRevision) SetChildren(name string, children []Revision) {
99 npr.childrenLock.Lock()
100 defer npr.childrenLock.Unlock()
101
102 npr.Children[name] = make([]Revision, len(children))
103 copy(npr.Children[name], children)
104}
105
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500106func (npr *NonPersistedRevision) GetAllChildren() map[string][]Revision {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400107 npr.childrenLock.Lock()
108 defer npr.childrenLock.Unlock()
109
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400110 return npr.Children
111}
112
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500113func (npr *NonPersistedRevision) GetChildren(name string) []Revision {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400114 npr.childrenLock.Lock()
115 defer npr.childrenLock.Unlock()
116
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500117 if _, exists := npr.Children[name]; exists {
118 return npr.Children[name]
119 }
120 return nil
121}
122
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400123func (npr *NonPersistedRevision) SetHash(hash string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500124 npr.mutex.Lock()
125 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400126 npr.Hash = hash
127}
128
129func (npr *NonPersistedRevision) GetHash() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500130 //npr.mutex.Lock()
131 //defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400132 return npr.Hash
133}
134
135func (npr *NonPersistedRevision) ClearHash() {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500136 npr.mutex.Lock()
137 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400138 npr.Hash = ""
139}
140
Stephane Barbarief7fc1782019-03-28 22:33:41 -0400141func (npr *NonPersistedRevision) GetName() string {
142 //npr.mutex.Lock()
143 //defer npr.mutex.Unlock()
144 return npr.Name
145}
146
147func (npr *NonPersistedRevision) SetName(name string) {
148 //npr.mutex.Lock()
149 //defer npr.mutex.Unlock()
150 npr.Name = name
151}
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400152func (npr *NonPersistedRevision) SetBranch(branch *Branch) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500153 npr.mutex.Lock()
154 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400155 npr.Branch = branch
156}
157
158func (npr *NonPersistedRevision) GetBranch() *Branch {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500159 npr.mutex.Lock()
160 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400161 return npr.Branch
162}
163
164func (npr *NonPersistedRevision) GetData() interface{} {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500165 npr.mutex.Lock()
166 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400167 if npr.Config == nil {
168 return nil
169 }
170 return npr.Config.Data
171}
172
Stephane Barbarie06c4a742018-10-01 11:09:32 -0400173func (npr *NonPersistedRevision) GetNode() *node {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500174 npr.mutex.Lock()
175 defer npr.mutex.Unlock()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400176 return npr.Branch.Node
177}
178
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500179func (npr *NonPersistedRevision) Finalize(skipOnExist bool) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400180 npr.Hash = npr.hashContent()
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400181}
182
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400183// hashContent generates a hash string based on the contents of the revision.
184// The string should be unique to avoid conflicts with other revisions
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400185func (npr *NonPersistedRevision) hashContent() string {
186 var buffer bytes.Buffer
187 var childrenKeys []string
188
189 if npr.Config != nil {
190 buffer.WriteString(npr.Config.Hash)
191 }
192
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400193 if npr.Name != "" {
194 buffer.WriteString(npr.Name)
195 }
196
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500197 for key := range npr.Children {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400198 childrenKeys = append(childrenKeys, key)
199 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500200
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400201 sort.Strings(childrenKeys)
202
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500203 if len(npr.Children) > 0 {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400204 // Loop through sorted Children keys
205 for _, key := range childrenKeys {
206 for _, child := range npr.Children[key] {
207 if child != nil && child.GetHash() != "" {
208 buffer.WriteString(child.GetHash())
209 }
210 }
211 }
212 }
213
214 return fmt.Sprintf("%x", md5.Sum(buffer.Bytes()))[:12]
215}
216
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400217// Get will retrieve the data for the current revision
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400218func (npr *NonPersistedRevision) Get(depth int) interface{} {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500219 // 1. Clone the data to avoid any concurrent access issues
220 // 2. The current rev might still be pointing to an old config
221 // thus, force the revision to get its latest value
222 latestRev := npr.GetBranch().GetLatest()
223 originalData := proto.Clone(latestRev.GetData().(proto.Message))
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500224 data := originalData
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400225
226 if depth != 0 {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400227 // FIXME: Traversing the struct through reflection sometimes corrupts the data.
228 // Unlike the original python implementation, golang structs are not lazy loaded.
229 // Keeping this non-critical logic for now, but Get operations should be forced to
230 // depth=0 to avoid going through the following loop.
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500231 for fieldName, field := range ChildrenFields(latestRev.GetData()) {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400232 childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
233 if field.IsContainer {
Stephane Barbarie3cb01222019-01-16 17:15:56 -0500234 for _, rev := range latestRev.GetChildren(fieldName) {
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400235 childData := rev.Get(depth - 1)
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400236 foundEntry := false
237 for i := 0; i < childDataHolder.Len(); i++ {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500238 cdh_if := childDataHolder.Index(i).Interface()
239 if cdh_if.(proto.Message).String() == childData.(proto.Message).String() {
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400240 foundEntry = true
241 break
242 }
243 }
244 if !foundEntry {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500245 // avoid duplicates by adding it only if the child was not found in the holder
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400246 childDataHolder = reflect.Append(childDataHolder, reflect.ValueOf(childData))
247 }
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400248 }
249 } else {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400250 if revs := npr.GetBranch().GetLatest().GetChildren(fieldName); revs != nil && len(revs) > 0 {
251 rev := revs[0]
Stephane Barbarie126101e2018-10-11 16:18:48 -0400252 if rev != nil {
253 childData := rev.Get(depth - 1)
254 if reflect.TypeOf(childData) == reflect.TypeOf(childDataHolder.Interface()) {
255 childDataHolder = reflect.ValueOf(childData)
256 }
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400257 }
258 }
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400259 }
260 // Merge child data with cloned object
261 reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
262 }
263 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400264
265 result := data
266
267 if result != nil {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500268 // We need to send back a copy of the retrieved object
269 result = proto.Clone(data.(proto.Message))
Stephane Barbarie126101e2018-10-11 16:18:48 -0400270 }
271
272 return result
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400273}
274
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400275// UpdateData will refresh the data content of the revision
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400276func (npr *NonPersistedRevision) UpdateData(data interface{}, branch *Branch) Revision {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500277 npr.mutex.Lock()
278 defer npr.mutex.Unlock()
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400279
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400280 // Do not update the revision if data is the same
Stephane Barbariedf5479f2019-01-29 22:13:00 -0500281 if npr.Config.Data != nil && npr.Config.hashData(npr.Root, data) == npr.Config.Hash {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500282 log.Debugw("stored-data-matches-latest", log.Fields{"stored": npr.Config.Data, "provided": data})
Stephane Barbariedf5479f2019-01-29 22:13:00 -0500283 return npr
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500284 }
285
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400286 // Construct a new revision based on the current one
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500287 newRev := NonPersistedRevision{}
288 newRev.Config = NewDataRevision(npr.Root, data)
289 newRev.Hash = npr.Hash
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400290 newRev.Root = npr.Root
291 newRev.Name = npr.Name
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500292 newRev.Branch = branch
293
294 newRev.Children = make(map[string][]Revision)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400295 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500296 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
297 }
298
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500299 newRev.Finalize(false)
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400300
301 return &newRev
302}
303
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400304// UpdateChildren will refresh the list of children with the provided ones
305// It will carefully go through the list and ensure that no child is lost
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400306func (npr *NonPersistedRevision) UpdateChildren(name string, children []Revision, branch *Branch) Revision {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500307 npr.mutex.Lock()
308 defer npr.mutex.Unlock()
309
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400310 // Construct a new revision based on the current one
311 updatedRev := &NonPersistedRevision{}
Stephane Barbarie933b09b2019-01-09 11:12:09 -0500312 updatedRev.Config = NewDataRevision(npr.Root, npr.Config.Data)
313 updatedRev.Hash = npr.Hash
314 updatedRev.Branch = branch
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400315 updatedRev.Name = npr.Name
316
317 updatedRev.Children = make(map[string][]Revision)
318 for entryName, childrenEntry := range branch.GetLatest().GetAllChildren() {
319 updatedRev.Children[entryName] = append(updatedRev.Children[entryName], childrenEntry...)
320 }
321
322 var updatedChildren []Revision
323
324 // Verify if the map contains already contains an entry matching the name value
325 // If so, we need to retain the contents of that entry and merge them with the provided children revision list
326 if existingChildren := branch.GetLatest().GetChildren(name); existingChildren != nil {
327 // Construct a map of unique child names with the respective index value
328 // for the children in the existing revision as well as the new ones
329 existingNames := make(map[string]int)
330 newNames := make(map[string]int)
331
332 for i, newChild := range children {
333 newNames[newChild.GetName()] = i
334 }
335
336 for i, existingChild := range existingChildren {
337 existingNames[existingChild.GetName()] = i
338
339 // If an existing entry is not in the new list, add it to the updated list, so it is not forgotten
340 if _, exists := newNames[existingChild.GetName()]; !exists {
341 updatedChildren = append(updatedChildren, existingChild)
342 }
343 }
344
345 log.Debugw("existing-children-names", log.Fields{"hash": npr.GetHash(), "names": existingNames})
346
347 // Merge existing and new children
348 for _, newChild := range children {
349 nameIndex, nameExists := existingNames[newChild.GetName()]
350
351 // Does the existing list contain a child with that name?
352 if nameExists {
353 // Check if the data has changed or not
354 if existingChildren[nameIndex].GetData().(proto.Message).String() != newChild.GetData().(proto.Message).String() {
355 // replace entry
356 newChild.GetNode().Root = existingChildren[nameIndex].GetNode().Root
357 updatedChildren = append(updatedChildren, newChild)
358 } else {
359 // keep existing entry
360 updatedChildren = append(updatedChildren, existingChildren[nameIndex])
361 }
362 } else {
363 // new entry ... just add it
364 updatedChildren = append(updatedChildren, newChild)
365 }
366 }
367
368 // Save children in new revision
369 updatedRev.SetChildren(name, updatedChildren)
370
371 updatedNames := make(map[string]int)
372 for i, updatedChild := range updatedChildren {
373 updatedNames[updatedChild.GetName()] = i
374 }
375
376 log.Debugw("updated-children-names", log.Fields{"hash": npr.GetHash(), "names": updatedNames})
377
378 } else {
379 // There are no children available, just save the provided ones
380 updatedRev.SetChildren(name, children)
381 }
382
Stephane Barbarie933b09b2019-01-09 11:12:09 -0500383 updatedRev.Finalize(false)
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400384
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500385 return updatedRev
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400386}
387
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400388// UpdateAllChildren will replace the current list of children with the provided ones
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400389func (npr *NonPersistedRevision) UpdateAllChildren(children map[string][]Revision, branch *Branch) Revision {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500390 npr.mutex.Lock()
391 defer npr.mutex.Unlock()
392
Stephane Barbariec53a2752019-03-08 17:50:10 -0500393 newRev := npr
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500394 newRev.Config = npr.Config
395 newRev.Hash = npr.Hash
396 newRev.Branch = branch
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400397 newRev.Name = npr.Name
398
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500399 newRev.Children = make(map[string][]Revision)
Stephane Barbariec53a2752019-03-08 17:50:10 -0500400 for entryName, childrenEntry := range children {
401 newRev.Children[entryName] = append(newRev.Children[entryName], childrenEntry...)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500402 }
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500403 newRev.Finalize(false)
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400404
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500405 return newRev
Stephane Barbarieec0919b2018-09-05 14:14:29 -0400406}
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400407
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400408// Drop is used to indicate when a revision is no longer required
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400409func (npr *NonPersistedRevision) Drop(txid string, includeConfig bool) {
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400410 log.Debugw("dropping-revision", log.Fields{"hash": npr.GetHash(), "name": npr.GetName()})
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400411 npr.discarded = true
412}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500413
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400414// ChildDrop will remove a child entry matching the provided parameters from the current revision
415func (npr *NonPersistedRevision) ChildDrop(childType string, childHash string) {
416 if childType != "" {
417 children := make([]Revision, len(npr.GetChildren(childType)))
418 copy(children, npr.GetChildren(childType))
419 for i, child := range children {
420 if child.GetHash() == childHash {
421 children = append(children[:i], children[i+1:]...)
422 npr.SetChildren(childType, children)
423 break
424 }
425 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500426 }
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400427}
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500428
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400429func (npr *NonPersistedRevision) LoadFromPersistence(path string, txid string, blobs map[string]*kvstore.KVPair) []Revision {
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500430 // stub... required by interface
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500431 return nil
432}
Stephane Barbariee0a4c792019-01-16 11:26:29 -0500433
434func (npr *NonPersistedRevision) SetupWatch(key string) {
435 // stub ... required by interface
Stephane Barbarief7fc1782019-03-28 22:33:41 -0400436}
437
438func (pr *NonPersistedRevision) StorageDrop(txid string, includeConfig bool) {
439 // stub ... required by interface
440}