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