blob: d4a86f47ee571275dafbf50be0a56d3927b7c908 [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
Chaitrashree G Sbe6ab942019-05-24 06:42:49 -0400153var proxyOperationTypes = []string{
154 "PROXY_GET",
155 "PROXY_LIST",
156 "PROXY_ADD",
157 "PROXY_UPDATE",
158 "PROXY_REMOVE",
159 "PROXY_CREATE",
160}
161
162func (t ProxyOperation) String() string {
163 return proxyOperationTypes[t]
164}
165
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400166// parseForControlledPath verifies if a proxy path matches a pattern
167// for locations that need to be access controlled.
168func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
169 // TODO: Add other path prefixes that may need control
170 if strings.HasPrefix(path, "/devices") ||
171 strings.HasPrefix(path, "/logical_devices") ||
172 strings.HasPrefix(path, "/adapters") {
173
174 split := strings.SplitN(path, "/", -1)
175 switch len(split) {
176 case 2:
177 controlled = false
178 pathLock = ""
179 break
180 case 3:
181 fallthrough
182 default:
183 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
184 controlled = true
185 }
186 }
187 return pathLock, controlled
188}
189
190// List will retrieve information from the data model at the specified path location
191// A list operation will force access to persistence storage
192func (p *Proxy) List(path string, depth int, deep bool, txid string) interface{} {
193 var effectivePath string
194 if path == "/" {
195 effectivePath = p.getFullPath()
196 } else {
197 effectivePath = p.getFullPath() + path
198 }
199
200 pathLock, controlled := p.parseForControlledPath(effectivePath)
201
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400202 log.Debugw("proxy-list", log.Fields{
203 "path": path,
204 "effective": effectivePath,
205 "pathLock": pathLock,
206 "controlled": controlled,
207 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400208
209 pac := PAC().ReservePath(effectivePath, p, pathLock)
210 defer PAC().ReleasePath(pathLock)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400211 p.Operation = PROXY_LIST
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400212 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400213 defer func(op ProxyOperation) {
214 pac.getProxy().Operation = op
215 }(PROXY_GET)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400216
217 rv := pac.List(path, depth, deep, txid, controlled)
218
219 return rv
220}
221
222// Get will retrieve information from the data model at the specified path location
223func (p *Proxy) Get(path string, depth int, deep bool, txid string) interface{} {
224 var effectivePath string
225 if path == "/" {
226 effectivePath = p.getFullPath()
227 } else {
228 effectivePath = p.getFullPath() + path
229 }
230
231 pathLock, controlled := p.parseForControlledPath(effectivePath)
232
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400233 log.Debugw("proxy-get", log.Fields{
234 "path": path,
235 "effective": effectivePath,
236 "pathLock": pathLock,
237 "controlled": controlled,
238 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400239
240 pac := PAC().ReservePath(effectivePath, p, pathLock)
241 defer PAC().ReleasePath(pathLock)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400242 p.Operation = PROXY_GET
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400243 pac.SetProxy(p)
244
245 rv := pac.Get(path, depth, deep, txid, controlled)
246
247 return rv
248}
249
250// Update will modify information in the data model at the specified location with the provided data
251func (p *Proxy) Update(path string, data interface{}, strict bool, txid string) interface{} {
252 if !strings.HasPrefix(path, "/") {
253 log.Errorf("invalid path: %s", path)
254 return nil
255 }
256 var fullPath string
257 var effectivePath string
258 if path == "/" {
259 fullPath = p.getPath()
260 effectivePath = p.getFullPath()
261 } else {
262 fullPath = p.getPath() + path
263 effectivePath = p.getFullPath() + path
264 }
265
266 pathLock, controlled := p.parseForControlledPath(effectivePath)
267
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400268 log.Debugw("proxy-update", log.Fields{
269 "path": path,
270 "effective": effectivePath,
271 "full": fullPath,
272 "pathLock": pathLock,
273 "controlled": controlled,
274 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400275
276 pac := PAC().ReservePath(effectivePath, p, pathLock)
277 defer PAC().ReleasePath(pathLock)
278
279 p.Operation = PROXY_UPDATE
280 pac.SetProxy(p)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400281 defer func(op ProxyOperation) {
282 pac.getProxy().Operation = op
283 }(PROXY_GET)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400284 log.Debugw("proxy-operation--update", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400285
286 return pac.Update(fullPath, data, strict, txid, controlled)
287}
288
289// AddWithID will insert new data at specified location.
290// This method also allows the user to specify the ID of the data entry to ensure
291// that access control is active while inserting the information.
292func (p *Proxy) AddWithID(path string, id string, data interface{}, txid string) interface{} {
293 if !strings.HasPrefix(path, "/") {
294 log.Errorf("invalid path: %s", path)
295 return nil
296 }
297 var fullPath string
298 var effectivePath string
299 if path == "/" {
300 fullPath = p.getPath()
301 effectivePath = p.getFullPath()
302 } else {
303 fullPath = p.getPath() + path
304 effectivePath = p.getFullPath() + path + "/" + id
305 }
306
307 pathLock, controlled := p.parseForControlledPath(effectivePath)
308
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400309 log.Debugw("proxy-add-with-id", log.Fields{
310 "path": path,
311 "effective": effectivePath,
312 "full": fullPath,
313 "pathLock": pathLock,
314 "controlled": controlled,
315 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400316
317 pac := PAC().ReservePath(path, p, pathLock)
318 defer PAC().ReleasePath(pathLock)
319
320 p.Operation = PROXY_ADD
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400321 defer func(op ProxyOperation) {
322 pac.getProxy().Operation = op
323 }(PROXY_GET)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400324
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400325 pac.SetProxy(p)
326
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400327 log.Debugw("proxy-operation--add", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400328
329 return pac.Add(fullPath, data, txid, controlled)
330}
331
332// Add will insert new data at specified location.
333func (p *Proxy) Add(path string, data interface{}, txid string) interface{} {
334 if !strings.HasPrefix(path, "/") {
335 log.Errorf("invalid path: %s", path)
336 return nil
337 }
338 var fullPath string
339 var effectivePath string
340 if path == "/" {
341 fullPath = p.getPath()
342 effectivePath = p.getFullPath()
343 } else {
344 fullPath = p.getPath() + path
345 effectivePath = p.getFullPath() + path
346 }
347
348 pathLock, controlled := p.parseForControlledPath(effectivePath)
349
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400350 log.Debugw("proxy-add", log.Fields{
351 "path": path,
352 "effective": effectivePath,
353 "full": fullPath,
354 "pathLock": pathLock,
355 "controlled": controlled,
356 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400357
358 pac := PAC().ReservePath(path, p, pathLock)
359 defer PAC().ReleasePath(pathLock)
360
361 p.Operation = PROXY_ADD
362 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400363 defer func(op ProxyOperation) {
364 pac.getProxy().Operation = op
365 }(PROXY_GET)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400366
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400367 log.Debugw("proxy-operation--add", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400368
369 return pac.Add(fullPath, data, txid, controlled)
370}
371
372// Remove will delete an entry at the specified location
373func (p *Proxy) Remove(path string, txid string) interface{} {
374 if !strings.HasPrefix(path, "/") {
375 log.Errorf("invalid path: %s", path)
376 return nil
377 }
378 var fullPath string
379 var effectivePath string
380 if path == "/" {
381 fullPath = p.getPath()
382 effectivePath = p.getFullPath()
383 } else {
384 fullPath = p.getPath() + path
385 effectivePath = p.getFullPath() + path
386 }
387
388 pathLock, controlled := p.parseForControlledPath(effectivePath)
389
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400390 log.Debugw("proxy-remove", log.Fields{
391 "path": path,
392 "effective": effectivePath,
393 "full": fullPath,
394 "pathLock": pathLock,
395 "controlled": controlled,
396 })
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400397
398 pac := PAC().ReservePath(effectivePath, p, pathLock)
399 defer PAC().ReleasePath(pathLock)
400
401 p.Operation = PROXY_REMOVE
402 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400403 defer func(op ProxyOperation) {
404 pac.getProxy().Operation = op
405 }(PROXY_GET)
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400406
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400407 log.Debugw("proxy-operation--remove", log.Fields{"operation": p.Operation})
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400408
409 return pac.Remove(fullPath, txid, controlled)
410}
411
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400412// CreateProxy to interact with specific path directly
413func (p *Proxy) CreateProxy(path string, exclusive bool) *Proxy {
414 if !strings.HasPrefix(path, "/") {
415 log.Errorf("invalid path: %s", path)
416 return nil
417 }
418
419 var fullPath string
420 var effectivePath string
421 if path == "/" {
422 fullPath = p.getPath()
423 effectivePath = p.getFullPath()
424 } else {
425 fullPath = p.getPath() + path
426 effectivePath = p.getFullPath() + path
427 }
428
429 pathLock, controlled := p.parseForControlledPath(effectivePath)
430
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400431 log.Debugw("proxy-create", log.Fields{
432 "path": path,
433 "effective": effectivePath,
434 "full": fullPath,
435 "pathLock": pathLock,
436 "controlled": controlled,
437 })
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400438
439 pac := PAC().ReservePath(path, p, pathLock)
440 defer PAC().ReleasePath(pathLock)
441
442 p.Operation = PROXY_CREATE
443 pac.SetProxy(p)
manikkaraj k9eb6cac2019-05-09 12:32:03 -0400444 defer func(op ProxyOperation) {
445 pac.getProxy().Operation = op
446 }(PROXY_GET)
Matt Jeanneret384d8c92019-05-06 14:27:31 -0400447
448 log.Debugw("proxy-operation--create-proxy", log.Fields{"operation": p.Operation})
449
450 return pac.CreateProxy(fullPath, exclusive, controlled)
451}
452
Matt Jeanneretcab955f2019-04-10 15:45:57 -0400453// OpenTransaction creates a new transaction branch to isolate operations made to the data model
454func (p *Proxy) OpenTransaction() *Transaction {
455 txid := p.GetRoot().MakeTxBranch()
456 return NewTransaction(p, txid)
457}
458
459// commitTransaction will apply and merge modifications made in the transaction branch to the data model
460func (p *Proxy) commitTransaction(txid string) {
461 p.GetRoot().FoldTxBranch(txid)
462}
463
464// cancelTransaction will terminate a transaction branch along will all changes within it
465func (p *Proxy) cancelTransaction(txid string) {
466 p.GetRoot().DeleteTxBranch(txid)
467}
468
469// CallbackFunction is a type used to define callback functions
470type CallbackFunction func(args ...interface{}) interface{}
471
472// CallbackTuple holds the function and arguments details of a callback
473type CallbackTuple struct {
474 callback CallbackFunction
475 args []interface{}
476}
477
478// Execute will process the a callback with its provided arguments
479func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
480 args := []interface{}{}
481
482 for _, ta := range tuple.args {
483 args = append(args, ta)
484 }
485
486 if contextArgs != nil {
487 for _, ca := range contextArgs {
488 args = append(args, ca)
489 }
490 }
491
492 return tuple.callback(args...)
493}
494
495// RegisterCallback associates a callback to the proxy
496func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
497 if p.getCallbacks(callbackType) == nil {
498 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
499 }
500 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
501 log.Debugf("value of function: %s", funcName)
502 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
503
504 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
505}
506
507// UnregisterCallback removes references to a callback within a proxy
508func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
509 if p.getCallbacks(callbackType) == nil {
510 log.Errorf("no such callback type - %s", callbackType.String())
511 return
512 }
513
514 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
515 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
516
517 log.Debugf("value of function: %s", funcName)
518
519 if p.getCallback(callbackType, funcHash) == nil {
520 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
521 return
522 }
523
524 p.DeleteCallback(callbackType, funcHash)
525}
526
527func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
528 defer func() {
529 if r := recover(); r != nil {
530 errStr := fmt.Sprintf("callback error occurred: %+v", r)
531 err = errors.New(errStr)
532 log.Error(errStr)
533 }
534 }()
535
536 result = callback.Execute(context)
537
538 return result, err
539}
540
541// InvokeCallbacks executes all callbacks associated to a specific type
542func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
543 callbackType := args[0].(CallbackType)
544 proceedOnError := args[1].(bool)
545 context := args[2:]
546
547 var err error
548
549 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
550 p.Lock()
551 for _, callback := range callbacks {
552 if result, err = p.invoke(callback, context); err != nil {
553 if !proceedOnError {
554 log.Info("An error occurred. Stopping callback invocation")
555 break
556 }
557 log.Info("An error occurred. Invoking next callback")
558 }
559 }
560 p.Unlock()
561 }
562
563 return result
564}