blob: 29334648827eeaa3ae8d295536d65d7d8099207e [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
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400150 PROXY_CREATE
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400151)
152
153// parseForControlledPath verifies if a proxy path matches a pattern
154// for locations that need to be access controlled.
155func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
156 // TODO: Add other path prefixes that may need control
157 if strings.HasPrefix(path, "/devices") ||
158 strings.HasPrefix(path, "/logical_devices") ||
159 strings.HasPrefix(path, "/adapters") {
160
161 split := strings.SplitN(path, "/", -1)
162 switch len(split) {
163 case 2:
164 controlled = false
165 pathLock = ""
166 break
167 case 3:
168 fallthrough
169 default:
170 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
171 controlled = true
172 }
173 }
174 return pathLock, controlled
175}
176
177// List will retrieve information from the data model at the specified path location
178// A list operation will force access to persistence storage
179func (p *Proxy) List(path string, depth int, deep bool, txid string) interface{} {
180 var effectivePath string
181 if path == "/" {
182 effectivePath = p.getFullPath()
183 } else {
184 effectivePath = p.getFullPath() + path
185 }
186
187 pathLock, controlled := p.parseForControlledPath(effectivePath)
188
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400189 log.Debugw("proxy-list", log.Fields{
190 "path": path,
191 "effective": effectivePath,
192 "pathLock": pathLock,
193 "controlled": controlled,
194 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400195
196 pac := PAC().ReservePath(effectivePath, p, pathLock)
197 defer PAC().ReleasePath(pathLock)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400198 p.Operation = PROXY_LIST
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400199 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400200 defer func(op ProxyOperation) {
201 pac.getProxy().Operation = op
202 }(PROXY_GET)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400203
204 rv := pac.List(path, depth, deep, txid, controlled)
205
206 return rv
207}
208
209// Get will retrieve information from the data model at the specified path location
210func (p *Proxy) Get(path string, depth int, deep bool, txid string) interface{} {
211 var effectivePath string
212 if path == "/" {
213 effectivePath = p.getFullPath()
214 } else {
215 effectivePath = p.getFullPath() + path
216 }
217
218 pathLock, controlled := p.parseForControlledPath(effectivePath)
219
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400220 log.Debugw("proxy-get", log.Fields{
221 "path": path,
222 "effective": effectivePath,
223 "pathLock": pathLock,
224 "controlled": controlled,
225 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400226
227 pac := PAC().ReservePath(effectivePath, p, pathLock)
228 defer PAC().ReleasePath(pathLock)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400229 p.Operation = PROXY_GET
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400230 pac.SetProxy(p)
231
232 rv := pac.Get(path, depth, deep, txid, controlled)
233
234 return rv
235}
236
237// Update will modify information in the data model at the specified location with the provided data
238func (p *Proxy) Update(path string, data interface{}, strict bool, txid string) interface{} {
239 if !strings.HasPrefix(path, "/") {
240 log.Errorf("invalid path: %s", path)
241 return nil
242 }
243 var fullPath string
244 var effectivePath string
245 if path == "/" {
246 fullPath = p.getPath()
247 effectivePath = p.getFullPath()
248 } else {
249 fullPath = p.getPath() + path
250 effectivePath = p.getFullPath() + path
251 }
252
253 pathLock, controlled := p.parseForControlledPath(effectivePath)
254
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400255 log.Debugw("proxy-update", log.Fields{
256 "path": path,
257 "effective": effectivePath,
258 "full": fullPath,
259 "pathLock": pathLock,
260 "controlled": controlled,
261 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400262
263 pac := PAC().ReservePath(effectivePath, p, pathLock)
264 defer PAC().ReleasePath(pathLock)
265
266 p.Operation = PROXY_UPDATE
267 pac.SetProxy(p)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400268 defer func(op ProxyOperation) {
269 pac.getProxy().Operation = op
270 }(PROXY_GET)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400271 log.Debugw("proxy-operation--update", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400272
273 return pac.Update(fullPath, data, strict, txid, controlled)
274}
275
276// AddWithID will insert new data at specified location.
277// This method also allows the user to specify the ID of the data entry to ensure
278// that access control is active while inserting the information.
279func (p *Proxy) AddWithID(path string, id string, data interface{}, txid string) interface{} {
280 if !strings.HasPrefix(path, "/") {
281 log.Errorf("invalid path: %s", path)
282 return nil
283 }
284 var fullPath string
285 var effectivePath string
286 if path == "/" {
287 fullPath = p.getPath()
288 effectivePath = p.getFullPath()
289 } else {
290 fullPath = p.getPath() + path
291 effectivePath = p.getFullPath() + path + "/" + id
292 }
293
294 pathLock, controlled := p.parseForControlledPath(effectivePath)
295
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400296 log.Debugw("proxy-add-with-id", log.Fields{
297 "path": path,
298 "effective": effectivePath,
299 "full": fullPath,
300 "pathLock": pathLock,
301 "controlled": controlled,
302 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400303
304 pac := PAC().ReservePath(path, p, pathLock)
305 defer PAC().ReleasePath(pathLock)
306
307 p.Operation = PROXY_ADD
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400308 defer func(op ProxyOperation) {
309 pac.getProxy().Operation = op
310 }(PROXY_GET)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400311
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400312 pac.SetProxy(p)
313
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400314 log.Debugw("proxy-operation--add", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400315
316 return pac.Add(fullPath, data, txid, controlled)
317}
318
319// Add will insert new data at specified location.
320func (p *Proxy) Add(path string, data interface{}, txid string) interface{} {
321 if !strings.HasPrefix(path, "/") {
322 log.Errorf("invalid path: %s", path)
323 return nil
324 }
325 var fullPath string
326 var effectivePath string
327 if path == "/" {
328 fullPath = p.getPath()
329 effectivePath = p.getFullPath()
330 } else {
331 fullPath = p.getPath() + path
332 effectivePath = p.getFullPath() + path
333 }
334
335 pathLock, controlled := p.parseForControlledPath(effectivePath)
336
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400337 log.Debugw("proxy-add", log.Fields{
338 "path": path,
339 "effective": effectivePath,
340 "full": fullPath,
341 "pathLock": pathLock,
342 "controlled": controlled,
343 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400344
345 pac := PAC().ReservePath(path, p, pathLock)
346 defer PAC().ReleasePath(pathLock)
347
348 p.Operation = PROXY_ADD
349 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400350 defer func(op ProxyOperation) {
351 pac.getProxy().Operation = op
352 }(PROXY_GET)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400353
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400354 log.Debugw("proxy-operation--add", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400355
356 return pac.Add(fullPath, data, txid, controlled)
357}
358
359// Remove will delete an entry at the specified location
360func (p *Proxy) Remove(path string, txid string) interface{} {
361 if !strings.HasPrefix(path, "/") {
362 log.Errorf("invalid path: %s", path)
363 return nil
364 }
365 var fullPath string
366 var effectivePath string
367 if path == "/" {
368 fullPath = p.getPath()
369 effectivePath = p.getFullPath()
370 } else {
371 fullPath = p.getPath() + path
372 effectivePath = p.getFullPath() + path
373 }
374
375 pathLock, controlled := p.parseForControlledPath(effectivePath)
376
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400377 log.Debugw("proxy-remove", log.Fields{
378 "path": path,
379 "effective": effectivePath,
380 "full": fullPath,
381 "pathLock": pathLock,
382 "controlled": controlled,
383 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400384
385 pac := PAC().ReservePath(effectivePath, p, pathLock)
386 defer PAC().ReleasePath(pathLock)
387
388 p.Operation = PROXY_REMOVE
389 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400390 defer func(op ProxyOperation) {
391 pac.getProxy().Operation = op
392 }(PROXY_GET)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400393
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400394 log.Debugw("proxy-operation--remove", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400395
396 return pac.Remove(fullPath, txid, controlled)
397}
398
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400399// CreateProxy to interact with specific path directly
400func (p *Proxy) CreateProxy(path string, exclusive bool) *Proxy {
401 if !strings.HasPrefix(path, "/") {
402 log.Errorf("invalid path: %s", path)
403 return nil
404 }
405
406 var fullPath string
407 var effectivePath string
408 if path == "/" {
409 fullPath = p.getPath()
410 effectivePath = p.getFullPath()
411 } else {
412 fullPath = p.getPath() + path
413 effectivePath = p.getFullPath() + path
414 }
415
416 pathLock, controlled := p.parseForControlledPath(effectivePath)
417
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400418 log.Debugw("proxy-create", log.Fields{
419 "path": path,
420 "effective": effectivePath,
421 "full": fullPath,
422 "pathLock": pathLock,
423 "controlled": controlled,
424 })
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400425
426 pac := PAC().ReservePath(path, p, pathLock)
427 defer PAC().ReleasePath(pathLock)
428
429 p.Operation = PROXY_CREATE
430 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400431 defer func(op ProxyOperation) {
432 pac.getProxy().Operation = op
433 }(PROXY_GET)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400434
435 log.Debugw("proxy-operation--create-proxy", log.Fields{"operation": p.Operation})
436
437 return pac.CreateProxy(fullPath, exclusive, controlled)
438}
439
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400440// OpenTransaction creates a new transaction branch to isolate operations made to the data model
441func (p *Proxy) OpenTransaction() *Transaction {
442 txid := p.GetRoot().MakeTxBranch()
443 return NewTransaction(p, txid)
444}
445
446// commitTransaction will apply and merge modifications made in the transaction branch to the data model
447func (p *Proxy) commitTransaction(txid string) {
448 p.GetRoot().FoldTxBranch(txid)
449}
450
451// cancelTransaction will terminate a transaction branch along will all changes within it
452func (p *Proxy) cancelTransaction(txid string) {
453 p.GetRoot().DeleteTxBranch(txid)
454}
455
456// CallbackFunction is a type used to define callback functions
457type CallbackFunction func(args ...interface{}) interface{}
458
459// CallbackTuple holds the function and arguments details of a callback
460type CallbackTuple struct {
461 callback CallbackFunction
462 args []interface{}
463}
464
465// Execute will process the a callback with its provided arguments
466func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
467 args := []interface{}{}
468
469 for _, ta := range tuple.args {
470 args = append(args, ta)
471 }
472
473 if contextArgs != nil {
474 for _, ca := range contextArgs {
475 args = append(args, ca)
476 }
477 }
478
479 return tuple.callback(args...)
480}
481
482// RegisterCallback associates a callback to the proxy
483func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
484 if p.getCallbacks(callbackType) == nil {
485 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
486 }
487 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
488 log.Debugf("value of function: %s", funcName)
489 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
490
491 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
492}
493
494// UnregisterCallback removes references to a callback within a proxy
495func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
496 if p.getCallbacks(callbackType) == nil {
497 log.Errorf("no such callback type - %s", callbackType.String())
498 return
499 }
500
501 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
502 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
503
504 log.Debugf("value of function: %s", funcName)
505
506 if p.getCallback(callbackType, funcHash) == nil {
507 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
508 return
509 }
510
511 p.DeleteCallback(callbackType, funcHash)
512}
513
514func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
515 defer func() {
516 if r := recover(); r != nil {
517 errStr := fmt.Sprintf("callback error occurred: %+v", r)
518 err = errors.New(errStr)
519 log.Error(errStr)
520 }
521 }()
522
523 result = callback.Execute(context)
524
525 return result, err
526}
527
528// InvokeCallbacks executes all callbacks associated to a specific type
529func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
530 callbackType := args[0].(CallbackType)
531 proceedOnError := args[1].(bool)
532 context := args[2:]
533
534 var err error
535
536 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
537 p.Lock()
538 for _, callback := range callbacks {
539 if result, err = p.invoke(callback, context); err != nil {
540 if !proceedOnError {
541 log.Info("An error occurred. Stopping callback invocation")
542 break
543 }
544 log.Info("An error occurred. Invoking next callback")
545 }
546 }
547 p.Unlock()
548 }
549
550 return result
551}