blob: 338ef6764c538cbeaea11751a087a2b274731b1f [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 "encoding/hex"
21 "encoding/json"
22 "github.com/golang/protobuf/proto"
23 "github.com/google/uuid"
24 "github.com/opencord/voltha-go/common/log"
25 "reflect"
26 "sync"
27)
28
29// Root is used to provide an abstraction to the base root structure
30type Root interface {
31 Node
32
33 ExecuteCallbacks()
34 AddCallback(callback CallbackFunction, args ...interface{})
35 AddNotificationCallback(callback CallbackFunction, args ...interface{})
36}
37
38// root points to the top of the data model tree or sub-tree identified by a proxy
39type root struct {
40 *node
41
42 Callbacks []CallbackTuple
43 NotificationCallbacks []CallbackTuple
44
45 DirtyNodes map[string][]*node
46 KvStore *Backend
47 Loading bool
48 RevisionClass interface{}
49
50 mutex sync.RWMutex
51}
52
53// NewRoot creates an new instance of a root object
54func NewRoot(initialData interface{}, kvStore *Backend) *root {
55 root := &root{}
56
57 root.KvStore = kvStore
58 root.DirtyNodes = make(map[string][]*node)
59 root.Loading = false
60
61 // If there is no storage in place just revert to
62 // a non persistent mechanism
63 if kvStore != nil {
64 root.RevisionClass = reflect.TypeOf(PersistedRevision{})
65 } else {
66 root.RevisionClass = reflect.TypeOf(NonPersistedRevision{})
67 }
68
69 root.Callbacks = []CallbackTuple{}
70 root.NotificationCallbacks = []CallbackTuple{}
71
72 root.node = NewNode(root, initialData, false, "")
73
74 return root
75}
76
77// MakeTxBranch creates a new transaction branch
78func (r *root) MakeTxBranch() string {
79 txidBin, _ := uuid.New().MarshalBinary()
80 txid := hex.EncodeToString(txidBin)[:12]
81
82 r.DirtyNodes[txid] = []*node{r.node}
83 r.node.MakeBranch(txid)
84
85 return txid
86}
87
88// DeleteTxBranch removes a transaction branch
89func (r *root) DeleteTxBranch(txid string) {
90 for _, dirtyNode := range r.DirtyNodes[txid] {
91 dirtyNode.DeleteBranch(txid)
92 }
93 delete(r.DirtyNodes, txid)
94 delete(r.node.Branches, txid)
95}
96
97// FoldTxBranch will merge the contents of a transaction branch with the root object
98func (r *root) FoldTxBranch(txid string) {
99 // Start by doing a dry run of the merge
100 // If that fails, it bails out and the branch is deleted
101 if _, err := r.node.MergeBranch(txid, true); err != nil {
102 // Merge operation fails
103 r.DeleteTxBranch(txid)
104 } else {
105 r.node.MergeBranch(txid, false)
106 r.ExecuteCallbacks()
107 r.DeleteTxBranch(txid)
108 }
109}
110
111// ExecuteCallbacks will invoke all the callbacks linked to root object
112func (r *root) ExecuteCallbacks() {
113 r.mutex.Lock()
114 log.Debugf("ExecuteCallbacks has the ROOT lock : %+v", r)
115 defer r.mutex.Unlock()
116 defer log.Debugf("ExecuteCallbacks released the ROOT lock : %+v", r)
117 for len(r.Callbacks) > 0 {
118 callback := r.Callbacks[0]
119 r.Callbacks = r.Callbacks[1:]
120 go callback.Execute(nil)
121 }
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400122 //for len(r.NotificationCallbacks) > 0 {
123 // callback := r.NotificationCallbacks[0]
124 // r.NotificationCallbacks = r.NotificationCallbacks[1:]
125 // go callback.Execute(nil)
126 //}
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400127}
128
129func (r *root) hasCallbacks() bool {
130 return len(r.Callbacks) == 0
131}
132
133// getCallbacks returns the available callbacks
134func (r *root) GetCallbacks() []CallbackTuple {
135 r.mutex.Lock()
136 log.Debugf("getCallbacks has the ROOT lock : %+v", r)
137 defer r.mutex.Unlock()
138 defer log.Debugf("getCallbacks released the ROOT lock : %+v", r)
139 return r.Callbacks
140}
141
142// getCallbacks returns the available notification callbacks
143func (r *root) GetNotificationCallbacks() []CallbackTuple {
144 r.mutex.Lock()
145 log.Debugf("GetNotificationCallbacks has the ROOT lock : %+v", r)
146 defer r.mutex.Unlock()
147 defer log.Debugf("GetNotificationCallbacks released the ROOT lock : %+v", r)
148 return r.NotificationCallbacks
149}
150
151// AddCallback inserts a new callback with its arguments
152func (r *root) AddCallback(callback CallbackFunction, args ...interface{}) {
153 r.mutex.Lock()
154 log.Debugf("AddCallback has the ROOT lock : %+v", r)
155 defer r.mutex.Unlock()
156 defer log.Debugf("AddCallback released the ROOT lock : %+v", r)
157 r.Callbacks = append(r.Callbacks, CallbackTuple{callback, args})
158}
159
160// AddNotificationCallback inserts a new notification callback with its arguments
161func (r *root) AddNotificationCallback(callback CallbackFunction, args ...interface{}) {
162 r.mutex.Lock()
163 log.Debugf("AddNotificationCallback has the ROOT lock : %+v", r)
164 defer r.mutex.Unlock()
165 defer log.Debugf("AddNotificationCallback released the ROOT lock : %+v", r)
166 r.NotificationCallbacks = append(r.NotificationCallbacks, CallbackTuple{callback, args})
167}
168
169func (r *root) syncParent(childRev Revision, txid string) {
170 data := proto.Clone(r.Proxy.ParentNode.Latest().GetData().(proto.Message))
171
172 for fieldName, _ := range ChildrenFields(data) {
173 childDataName, childDataHolder := GetAttributeValue(data, fieldName, 0)
174 if reflect.TypeOf(childRev.GetData()) == reflect.TypeOf(childDataHolder.Interface()) {
175 childDataHolder = reflect.ValueOf(childRev.GetData())
176 reflect.ValueOf(data).Elem().FieldByName(childDataName).Set(childDataHolder)
177 }
178 }
179
180 r.Proxy.ParentNode.Latest().SetConfig(NewDataRevision(r.Proxy.ParentNode.Root, data))
181 r.Proxy.ParentNode.Latest(txid).Finalize(false)
182}
183
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400184// Update modifies the content of an object at a given path with the provided data
185func (r *root) Update(path string, data interface{}, strict bool, txid string, makeBranch MakeBranchFunction) Revision {
186 var result Revision
187
188 if makeBranch != nil {
189 // TODO: raise error
190 }
191
192 if r.hasCallbacks() {
193 // TODO: raise error
194 }
195
196 if txid != "" {
197 trackDirty := func(node *node) *Branch {
198 r.DirtyNodes[txid] = append(r.DirtyNodes[txid], node)
199 return node.MakeBranch(txid)
200 }
201 result = r.node.Update(path, data, strict, txid, trackDirty)
202 } else {
203 result = r.node.Update(path, data, strict, "", nil)
204 }
205
206 if result != nil {
207 if r.Proxy.FullPath != r.Proxy.Path {
208 r.syncParent(result, txid)
209 } else {
210 result.Finalize(false)
211 }
212 }
213
214 r.node.GetRoot().ExecuteCallbacks()
215
216 return result
217}
218
219// Add creates a new object at the given path with the provided data
220func (r *root) Add(path string, data interface{}, txid string, makeBranch MakeBranchFunction) Revision {
221 var result Revision
222
223 if makeBranch != nil {
224 // TODO: raise error
225 }
226
227 if r.hasCallbacks() {
228 // TODO: raise error
229 }
230
231 if txid != "" {
232 trackDirty := func(node *node) *Branch {
233 r.DirtyNodes[txid] = append(r.DirtyNodes[txid], node)
234 return node.MakeBranch(txid)
235 }
236 result = r.node.Add(path, data, txid, trackDirty)
237 } else {
238 result = r.node.Add(path, data, "", nil)
239 }
240
241 if result != nil {
242 result.Finalize(true)
243 r.node.GetRoot().ExecuteCallbacks()
244 }
245 return result
246}
247
248// Remove discards an object at a given path
249func (r *root) Remove(path string, txid string, makeBranch MakeBranchFunction) Revision {
250 var result Revision
251
252 if makeBranch != nil {
253 // TODO: raise error
254 }
255
256 if r.hasCallbacks() {
257 // TODO: raise error
258 }
259
260 if txid != "" {
261 trackDirty := func(node *node) *Branch {
262 r.DirtyNodes[txid] = append(r.DirtyNodes[txid], node)
263 return node.MakeBranch(txid)
264 }
265 result = r.node.Remove(path, txid, trackDirty)
266 } else {
267 result = r.node.Remove(path, "", nil)
268 }
269
270 r.node.GetRoot().ExecuteCallbacks()
271
272 return result
273}
274
275// MakeLatest updates a branch with the latest node revision
276func (r *root) MakeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple) {
277 r.makeLatest(branch, revision, changeAnnouncement)
278}
279
280func (r *root) MakeRevision(branch *Branch, data interface{}, children map[string][]Revision) Revision {
281 if r.RevisionClass.(reflect.Type) == reflect.TypeOf(PersistedRevision{}) {
282 return NewPersistedRevision(branch, data, children)
283 }
284
285 return NewNonPersistedRevision(r, branch, data, children)
286}
287
288func (r *root) makeLatest(branch *Branch, revision Revision, changeAnnouncement []ChangeTuple) {
289 r.node.makeLatest(branch, revision, changeAnnouncement)
290
291 if r.KvStore != nil && branch.Txid == "" {
292 tags := make(map[string]string)
293 for k, v := range r.node.Tags {
294 tags[k] = v.GetHash()
295 }
296 data := &rootData{
297 Latest: branch.GetLatest().GetHash(),
298 Tags: tags,
299 }
300 if blob, err := json.Marshal(data); err != nil {
301 // TODO report error
302 } else {
303 log.Debugf("Changing root to : %s", string(blob))
304 if err := r.KvStore.Put("root", blob); err != nil {
305 log.Errorf("failed to properly put value in kvstore - err: %s", err.Error())
306 }
307 }
308 }
309}
310
311type rootData struct {
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400312 Latest string `json:"latest"`
313 Tags map[string]string `json:"tags"`
314}