blob: 86d426a548828cb0c530995b7a4d123f4b74b426 [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 "crypto/md5"
21 "errors"
22 "fmt"
23 "github.com/opencord/voltha-go/common/log"
24 "reflect"
25 "runtime"
26 "strings"
27 "sync"
28)
29
30// OperationContext holds details on the information used during an operation
31type OperationContext struct {
32 Path string
33 Data interface{}
34 FieldName string
35 ChildKey string
36}
37
38// NewOperationContext instantiates a new OperationContext structure
39func NewOperationContext(path string, data interface{}, fieldName string, childKey string) *OperationContext {
40 oc := &OperationContext{
41 Path: path,
42 Data: data,
43 FieldName: fieldName,
44 ChildKey: childKey,
45 }
46 return oc
47}
48
49// Update applies new data to the context structure
50func (oc *OperationContext) Update(data interface{}) *OperationContext {
51 oc.Data = data
52 return oc
53}
54
55// Proxy holds the information for a specific location with the data model
56type Proxy struct {
57 sync.RWMutex
58 Root *root
59 Node *node
60 ParentNode *node
61 Path string
62 FullPath string
63 Exclusive bool
64 Callbacks map[CallbackType]map[string]*CallbackTuple
65 Operation ProxyOperation
66}
67
68// NewProxy instantiates a new proxy to a specific location
69func NewProxy(root *root, node *node, parentNode *node, path string, fullPath string, exclusive bool) *Proxy {
70 callbacks := make(map[CallbackType]map[string]*CallbackTuple)
71 if fullPath == "/" {
72 fullPath = ""
73 }
74 p := &Proxy{
75 Root: root,
76 Node: node,
77 ParentNode: parentNode,
78 Exclusive: exclusive,
79 Path: path,
80 FullPath: fullPath,
81 Callbacks: callbacks,
82 }
83 return p
84}
85
86// GetRoot returns the root attribute of the proxy
87func (p *Proxy) GetRoot() *root {
88 return p.Root
89}
90
91// getPath returns the path attribute of the proxy
92func (p *Proxy) getPath() string {
93 return p.Path
94}
95
96// getFullPath returns the full path attribute of the proxy
97func (p *Proxy) getFullPath() string {
98 return p.FullPath
99}
100
101// getCallbacks returns the full list of callbacks associated to the proxy
102func (p *Proxy) getCallbacks(callbackType CallbackType) map[string]*CallbackTuple {
103 if cb, exists := p.Callbacks[callbackType]; exists {
104 return cb
105 }
106 return nil
107}
108
109// getCallback returns a specific callback matching the type and function hash
110func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
111 p.Lock()
112 defer p.Unlock()
113 if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
114 return tuple
115 }
116 return nil
117}
118
119// setCallbacks applies a callbacks list to a type
120func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
121 p.Lock()
122 defer p.Unlock()
123 p.Callbacks[callbackType] = callbacks
124}
125
126// setCallback applies a callback to a type and hash value
127func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
128 p.Lock()
129 defer p.Unlock()
130 p.Callbacks[callbackType][funcHash] = tuple
131}
132
133// DeleteCallback removes a callback matching the type and hash
134func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
135 p.Lock()
136 defer p.Unlock()
137 delete(p.Callbacks[callbackType], funcHash)
138}
139
140// CallbackType is an enumerated value to express when a callback should be executed
141type ProxyOperation uint8
142
143// Enumerated list of callback types
144const (
145 PROXY_GET ProxyOperation = iota
146 PROXY_LIST
147 PROXY_ADD
148 PROXY_UPDATE
149 PROXY_REMOVE
150)
151
152// parseForControlledPath verifies if a proxy path matches a pattern
153// for locations that need to be access controlled.
154func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
155 // TODO: Add other path prefixes that may need control
156 if strings.HasPrefix(path, "/devices") ||
157 strings.HasPrefix(path, "/logical_devices") ||
158 strings.HasPrefix(path, "/adapters") {
159
160 split := strings.SplitN(path, "/", -1)
161 switch len(split) {
162 case 2:
163 controlled = false
164 pathLock = ""
165 break
166 case 3:
167 fallthrough
168 default:
169 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
170 controlled = true
171 }
172 }
173 return pathLock, controlled
174}
175
176// List will retrieve information from the data model at the specified path location
177// A list operation will force access to persistence storage
178func (p *Proxy) List(path string, depth int, deep bool, txid string) interface{} {
179 var effectivePath string
180 if path == "/" {
181 effectivePath = p.getFullPath()
182 } else {
183 effectivePath = p.getFullPath() + path
184 }
185
186 pathLock, controlled := p.parseForControlledPath(effectivePath)
187
188 log.Debugf("Path: %s, Effective: %s, PathLock: %s", path, effectivePath, pathLock)
189
190 pac := PAC().ReservePath(effectivePath, p, pathLock)
191 defer PAC().ReleasePath(pathLock)
192 pac.SetProxy(p)
193
194 rv := pac.List(path, depth, deep, txid, controlled)
195
196 return rv
197}
198
199// Get will retrieve information from the data model at the specified path location
200func (p *Proxy) Get(path string, depth int, deep bool, txid string) interface{} {
201 var effectivePath string
202 if path == "/" {
203 effectivePath = p.getFullPath()
204 } else {
205 effectivePath = p.getFullPath() + path
206 }
207
208 pathLock, controlled := p.parseForControlledPath(effectivePath)
209
210 log.Debugf("Path: %s, Effective: %s, PathLock: %s", path, effectivePath, pathLock)
211
212 pac := PAC().ReservePath(effectivePath, p, pathLock)
213 defer PAC().ReleasePath(pathLock)
214 pac.SetProxy(p)
215
216 rv := pac.Get(path, depth, deep, txid, controlled)
217
218 return rv
219}
220
221// Update will modify information in the data model at the specified location with the provided data
222func (p *Proxy) Update(path string, data interface{}, strict bool, txid string) interface{} {
223 if !strings.HasPrefix(path, "/") {
224 log.Errorf("invalid path: %s", path)
225 return nil
226 }
227 var fullPath string
228 var effectivePath string
229 if path == "/" {
230 fullPath = p.getPath()
231 effectivePath = p.getFullPath()
232 } else {
233 fullPath = p.getPath() + path
234 effectivePath = p.getFullPath() + path
235 }
236
237 pathLock, controlled := p.parseForControlledPath(effectivePath)
238
239 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s, Controlled: %b", path, effectivePath, fullPath, pathLock, controlled)
240
241 pac := PAC().ReservePath(effectivePath, p, pathLock)
242 defer PAC().ReleasePath(pathLock)
243
244 p.Operation = PROXY_UPDATE
245 pac.SetProxy(p)
246
247 log.Debugw("proxy-operation--update", log.Fields{"operation":p.Operation})
248
249 return pac.Update(fullPath, data, strict, txid, controlled)
250}
251
252// AddWithID will insert new data at specified location.
253// This method also allows the user to specify the ID of the data entry to ensure
254// that access control is active while inserting the information.
255func (p *Proxy) AddWithID(path string, id string, data interface{}, txid string) interface{} {
256 if !strings.HasPrefix(path, "/") {
257 log.Errorf("invalid path: %s", path)
258 return nil
259 }
260 var fullPath string
261 var effectivePath string
262 if path == "/" {
263 fullPath = p.getPath()
264 effectivePath = p.getFullPath()
265 } else {
266 fullPath = p.getPath() + path
267 effectivePath = p.getFullPath() + path + "/" + id
268 }
269
270 pathLock, controlled := p.parseForControlledPath(effectivePath)
271
272 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
273
274 pac := PAC().ReservePath(path, p, pathLock)
275 defer PAC().ReleasePath(pathLock)
276
277 p.Operation = PROXY_ADD
278 pac.SetProxy(p)
279
280 log.Debugw("proxy-operation--add", log.Fields{"operation":p.Operation})
281
282 return pac.Add(fullPath, data, txid, controlled)
283}
284
285// Add will insert new data at specified location.
286func (p *Proxy) Add(path string, data interface{}, txid string) interface{} {
287 if !strings.HasPrefix(path, "/") {
288 log.Errorf("invalid path: %s", path)
289 return nil
290 }
291 var fullPath string
292 var effectivePath string
293 if path == "/" {
294 fullPath = p.getPath()
295 effectivePath = p.getFullPath()
296 } else {
297 fullPath = p.getPath() + path
298 effectivePath = p.getFullPath() + path
299 }
300
301 pathLock, controlled := p.parseForControlledPath(effectivePath)
302
303 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
304
305 pac := PAC().ReservePath(path, p, pathLock)
306 defer PAC().ReleasePath(pathLock)
307
308 p.Operation = PROXY_ADD
309 pac.SetProxy(p)
310
311 log.Debugw("proxy-operation--add", log.Fields{"operation":p.Operation})
312
313 return pac.Add(fullPath, data, txid, controlled)
314}
315
316// Remove will delete an entry at the specified location
317func (p *Proxy) Remove(path string, txid string) interface{} {
318 if !strings.HasPrefix(path, "/") {
319 log.Errorf("invalid path: %s", path)
320 return nil
321 }
322 var fullPath string
323 var effectivePath string
324 if path == "/" {
325 fullPath = p.getPath()
326 effectivePath = p.getFullPath()
327 } else {
328 fullPath = p.getPath() + path
329 effectivePath = p.getFullPath() + path
330 }
331
332 pathLock, controlled := p.parseForControlledPath(effectivePath)
333
334 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
335
336 pac := PAC().ReservePath(effectivePath, p, pathLock)
337 defer PAC().ReleasePath(pathLock)
338
339 p.Operation = PROXY_REMOVE
340 pac.SetProxy(p)
341
342 log.Debugw("proxy-operation--remove", log.Fields{"operation":p.Operation})
343
344 return pac.Remove(fullPath, txid, controlled)
345}
346
347// OpenTransaction creates a new transaction branch to isolate operations made to the data model
348func (p *Proxy) OpenTransaction() *Transaction {
349 txid := p.GetRoot().MakeTxBranch()
350 return NewTransaction(p, txid)
351}
352
353// commitTransaction will apply and merge modifications made in the transaction branch to the data model
354func (p *Proxy) commitTransaction(txid string) {
355 p.GetRoot().FoldTxBranch(txid)
356}
357
358// cancelTransaction will terminate a transaction branch along will all changes within it
359func (p *Proxy) cancelTransaction(txid string) {
360 p.GetRoot().DeleteTxBranch(txid)
361}
362
363// CallbackFunction is a type used to define callback functions
364type CallbackFunction func(args ...interface{}) interface{}
365
366// CallbackTuple holds the function and arguments details of a callback
367type CallbackTuple struct {
368 callback CallbackFunction
369 args []interface{}
370}
371
372// Execute will process the a callback with its provided arguments
373func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
374 args := []interface{}{}
375
376 for _, ta := range tuple.args {
377 args = append(args, ta)
378 }
379
380 if contextArgs != nil {
381 for _, ca := range contextArgs {
382 args = append(args, ca)
383 }
384 }
385
386 return tuple.callback(args...)
387}
388
389// RegisterCallback associates a callback to the proxy
390func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
391 if p.getCallbacks(callbackType) == nil {
392 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
393 }
394 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
395 log.Debugf("value of function: %s", funcName)
396 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
397
398 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
399}
400
401// UnregisterCallback removes references to a callback within a proxy
402func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
403 if p.getCallbacks(callbackType) == nil {
404 log.Errorf("no such callback type - %s", callbackType.String())
405 return
406 }
407
408 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
409 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
410
411 log.Debugf("value of function: %s", funcName)
412
413 if p.getCallback(callbackType, funcHash) == nil {
414 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
415 return
416 }
417
418 p.DeleteCallback(callbackType, funcHash)
419}
420
421func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
422 defer func() {
423 if r := recover(); r != nil {
424 errStr := fmt.Sprintf("callback error occurred: %+v", r)
425 err = errors.New(errStr)
426 log.Error(errStr)
427 }
428 }()
429
430 result = callback.Execute(context)
431
432 return result, err
433}
434
435// InvokeCallbacks executes all callbacks associated to a specific type
436func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
437 callbackType := args[0].(CallbackType)
438 proceedOnError := args[1].(bool)
439 context := args[2:]
440
441 var err error
442
443 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
444 p.Lock()
445 for _, callback := range callbacks {
446 if result, err = p.invoke(callback, context); err != nil {
447 if !proceedOnError {
448 log.Info("An error occurred. Stopping callback invocation")
449 break
450 }
451 log.Info("An error occurred. Invoking next callback")
452 }
453 }
454 p.Unlock()
455 }
456
457 return result
458}