blob: 65da5613f63eaa7fbc76b2c0a439ba687ef7d869 [file] [log] [blame]
khenaidoobf6e7bb2018-08-14 22:27:29 -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 */
Stephane Barbariedc5022d2018-11-19 15:21:44 -050016
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040017package model
18
19import (
Stephane Barbarie694e2b92018-09-07 12:17:36 -040020 "crypto/md5"
Stephane Barbarie694e2b92018-09-07 12:17:36 -040021 "errors"
khenaidoob9203542018-09-17 22:56:37 -040022 "fmt"
23 "github.com/opencord/voltha-go/common/log"
24 "reflect"
Stephane Barbarie694e2b92018-09-07 12:17:36 -040025 "runtime"
khenaidoob9203542018-09-17 22:56:37 -040026 "strings"
Stephane Barbariea188d942018-10-16 16:43:04 -040027 "sync"
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040028)
29
Stephane Barbariedc5022d2018-11-19 15:21:44 -050030// OperationContext holds details on the information used during an operation
Stephane Barbarieec0919b2018-09-05 14:14:29 -040031type OperationContext struct {
32 Path string
33 Data interface{}
34 FieldName string
35 ChildKey string
36}
37
Stephane Barbariedc5022d2018-11-19 15:21:44 -050038// NewOperationContext instantiates a new OperationContext structure
Stephane Barbarieec0919b2018-09-05 14:14:29 -040039func 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
Stephane Barbariedc5022d2018-11-19 15:21:44 -050049// Update applies new data to the context structure
Stephane Barbarieec0919b2018-09-05 14:14:29 -040050func (oc *OperationContext) Update(data interface{}) *OperationContext {
51 oc.Data = data
52 return oc
53}
54
Stephane Barbariedc5022d2018-11-19 15:21:44 -050055// Proxy holds the information for a specific location with the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040056type Proxy struct {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050057 sync.RWMutex
Stephane Barbarie126101e2018-10-11 16:18:48 -040058 Root *root
Stephane Barbariedc5022d2018-11-19 15:21:44 -050059 Node *node
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040060 Path string
Stephane Barbariea188d942018-10-16 16:43:04 -040061 FullPath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040062 Exclusive bool
Stephane Barbariedc5022d2018-11-19 15:21:44 -050063 Callbacks map[CallbackType]map[string]*CallbackTuple
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040064}
65
Stephane Barbariedc5022d2018-11-19 15:21:44 -050066// NewProxy instantiates a new proxy to a specific location
67func NewProxy(root *root, node *node, path string, fullPath string, exclusive bool) *Proxy {
68 callbacks := make(map[CallbackType]map[string]*CallbackTuple)
Stephane Barbariea188d942018-10-16 16:43:04 -040069 if fullPath == "/" {
70 fullPath = ""
71 }
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040072 p := &Proxy{
73 Root: root,
Stephane Barbariedc5022d2018-11-19 15:21:44 -050074 Node: node,
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040075 Exclusive: exclusive,
76 Path: path,
Stephane Barbariea188d942018-10-16 16:43:04 -040077 FullPath: fullPath,
Stephane Barbarie694e2b92018-09-07 12:17:36 -040078 Callbacks: callbacks,
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040079 }
80 return p
81}
82
Stephane Barbariedc5022d2018-11-19 15:21:44 -050083// GetRoot returns the root attribute of the proxy
84func (p *Proxy) GetRoot() *root {
85 p.Lock()
86 defer p.Unlock()
87 return p.Root
88}
89
90// getPath returns the path attribute of the proxy
91func (p *Proxy) getPath() string {
92 p.Lock()
93 defer p.Unlock()
94 return p.Path
95}
96
97// getFullPath returns the full path attribute of the proxy
98func (p *Proxy) getFullPath() string {
99 p.Lock()
100 defer p.Unlock()
101 return p.FullPath
102}
103
104// getCallbacks returns the full list of callbacks associated to the proxy
105func (p *Proxy) getCallbacks(callbackType CallbackType) map[string]*CallbackTuple {
106 p.Lock()
107 defer p.Unlock()
108 if cb, exists := p.Callbacks[callbackType]; exists {
109 return cb
110 }
111 return nil
112}
113
114// getCallback returns a specific callback matching the type and function hash
115func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
116 p.Lock()
117 defer p.Unlock()
118 if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
119 return tuple
120 }
121 return nil
122}
123
124// setCallbacks applies a callbacks list to a type
125func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
126 p.Lock()
127 defer p.Unlock()
128 p.Callbacks[callbackType] = callbacks
129}
130
131// setCallback applies a callback to a type and hash value
132func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
133 p.Lock()
134 defer p.Unlock()
135 p.Callbacks[callbackType][funcHash] = tuple
136}
137
138// DeleteCallback removes a callback matching the type and hash
139func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
140 p.Lock()
141 defer p.Unlock()
142 delete(p.Callbacks[callbackType], funcHash)
143}
144
145// parseForControlledPath verifies if a proxy path matches a pattern
146// for locations that need to be access controlled.
Stephane Barbariea188d942018-10-16 16:43:04 -0400147func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500148 // TODO: Add other path prefixes that may need control
149 if strings.HasPrefix(path, "/devices") || strings.HasPrefix(path, "/logical_devices"){
Stephane Barbariea188d942018-10-16 16:43:04 -0400150 split := strings.SplitN(path, "/", -1)
151 switch len(split) {
152 case 2:
153 controlled = false
154 pathLock = ""
155 break
156 case 3:
157 fallthrough
158 default:
159 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
160 controlled = true
161 }
162 }
163 return pathLock, controlled
164}
165
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500166// Get will retrieve information from the data model at the specified path location
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400167func (p *Proxy) Get(path string, depth int, deep bool, txid string) interface{} {
Stephane Barbariea188d942018-10-16 16:43:04 -0400168 var effectivePath string
169 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500170 effectivePath = p.getFullPath()
Stephane Barbariea188d942018-10-16 16:43:04 -0400171 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500172 effectivePath = p.getFullPath() + path
Stephane Barbariea188d942018-10-16 16:43:04 -0400173 }
174
175 pathLock, controlled := p.parseForControlledPath(effectivePath)
176
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500177 log.Debugf("Path: %s, Effective: %s, PathLock: %s", path, effectivePath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400178
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500179 pac := PAC().ReservePath(effectivePath, p, pathLock)
180 defer PAC().ReleasePath(pathLock)
181 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400182
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500183 rv := pac.Get(path, depth, deep, txid, controlled)
184
185 return rv
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400186}
187
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500188// Update will modify information in the data model at the specified location with the provided data
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400189func (p *Proxy) Update(path string, data interface{}, strict bool, txid string) interface{} {
190 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400191 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400192 return nil
193 }
194 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400195 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400196 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500197 fullPath = p.getPath()
198 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400199 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500200 fullPath = p.getPath() + path
201 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400202 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400203
Stephane Barbariea188d942018-10-16 16:43:04 -0400204 pathLock, controlled := p.parseForControlledPath(effectivePath)
205
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500206 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400207
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500208 pac := PAC().ReservePath(effectivePath, p, pathLock)
209 defer PAC().ReleasePath(pathLock)
210 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400211
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500212 return pac.Update(fullPath, data, strict, txid, controlled)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400213}
214
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500215// AddWithID will insert new data at specified location.
216// This method also allows the user to specify the ID of the data entry to ensure
217// that access control is active while inserting the information.
218func (p *Proxy) AddWithID(path string, id string, data interface{}, txid string) interface{} {
219 if !strings.HasPrefix(path, "/") {
220 log.Errorf("invalid path: %s", path)
221 return nil
222 }
223 var fullPath string
224 var effectivePath string
225 if path == "/" {
226 fullPath = p.getPath()
227 effectivePath = p.getFullPath()
228 } else {
229 fullPath = p.getPath() + path
230 effectivePath = p.getFullPath() + path + "/" + id
231 }
232
233 pathLock, controlled := p.parseForControlledPath(effectivePath)
234
235 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
236
237 pac := PAC().ReservePath(path, p, pathLock)
238 defer PAC().ReleasePath(pathLock)
239 pac.SetProxy(p)
240
241 return pac.Add(fullPath, data, txid, controlled)
242}
243
244// Add will insert new data at specified location.
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400245func (p *Proxy) Add(path string, data interface{}, txid string) interface{} {
246 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400247 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400248 return nil
249 }
250 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400251 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400252 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500253 fullPath = p.getPath()
254 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400255 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500256 fullPath = p.getPath() + path
257 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400258 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400259
260 pathLock, controlled := p.parseForControlledPath(effectivePath)
261
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500262 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400263
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500264 pac := PAC().ReservePath(path, p, pathLock)
265 defer PAC().ReleasePath(pathLock)
266 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400267
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500268 return pac.Add(fullPath, data, txid, controlled)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400269}
270
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500271// Remove will delete an entry at the specified location
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400272func (p *Proxy) Remove(path string, txid string) interface{} {
273 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400274 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400275 return nil
276 }
277 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400278 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400279 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500280 fullPath = p.getPath()
281 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400282 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500283 fullPath = p.getPath() + path
284 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400285 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400286
287 pathLock, controlled := p.parseForControlledPath(effectivePath)
288
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500289 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400290
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500291 pac := PAC().ReservePath(effectivePath, p, pathLock)
292 defer PAC().ReleasePath(pathLock)
293 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400294
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500295 return pac.Remove(fullPath, txid, controlled)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400296}
297
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500298// OpenTransaction creates a new transaction branch to isolate operations made to the data model
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400299func (p *Proxy) OpenTransaction() *Transaction {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500300 txid := p.GetRoot().MakeTxBranch()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400301 return NewTransaction(p, txid)
302}
303
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500304// commitTransaction will apply and merge modifications made in the transaction branch to the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400305func (p *Proxy) commitTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500306 p.GetRoot().FoldTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400307}
308
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500309// cancelTransaction will terminate a transaction branch along will all changes within it
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400310func (p *Proxy) cancelTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500311 p.GetRoot().DeleteTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400312}
313
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500314// CallbackFunction is a type used to define callback functions
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400315type CallbackFunction func(args ...interface{}) interface{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500316
317// CallbackTuple holds the function and arguments details of a callback
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400318type CallbackTuple struct {
319 callback CallbackFunction
320 args []interface{}
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400321}
322
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500323// Execute will process the a callback with its provided arguments
324func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400325 args := []interface{}{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500326
327 for _, ta := range tuple.args {
328 args = append(args, ta)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400329 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500330
331 if contextArgs != nil {
332 for _, ca := range contextArgs {
333 args = append(args, ca)
334 }
335 }
336
Stephane Barbarie126101e2018-10-11 16:18:48 -0400337 return tuple.callback(args...)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400338}
339
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500340// RegisterCallback associates a callback to the proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400341func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500342 if p.getCallbacks(callbackType) == nil {
343 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400344 }
345 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
346 log.Debugf("value of function: %s", funcName)
347 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
348
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500349 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400350}
351
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500352// UnregisterCallback removes references to a callback within a proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400353func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500354 if p.getCallbacks(callbackType) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400355 log.Errorf("no such callback type - %s", callbackType.String())
356 return
357 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500358
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400359 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400360 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500361
362 log.Debugf("value of function: %s", funcName)
363
364 if p.getCallback(callbackType, funcHash) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400365 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
366 return
367 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500368
369 p.DeleteCallback(callbackType, funcHash)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400370}
371
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500372func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400373 defer func() {
374 if r := recover(); r != nil {
375 errStr := fmt.Sprintf("callback error occurred: %+v", r)
376 err = errors.New(errStr)
377 log.Error(errStr)
378 }
379 }()
380
381 result = callback.Execute(context)
382
383 return result, err
384}
385
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500386// InvokeCallbacks executes all callbacks associated to a specific type
Stephane Barbarie126101e2018-10-11 16:18:48 -0400387func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400388 callbackType := args[0].(CallbackType)
Stephane Barbarie126101e2018-10-11 16:18:48 -0400389 proceedOnError := args[1].(bool)
390 context := args[2:]
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400391
392 var err error
393
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500394 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
395 p.Lock()
396 for _, callback := range callbacks {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400397 if result, err = p.invoke(callback, context); err != nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400398 if !proceedOnError {
399 log.Info("An error occurred. Stopping callback invocation")
400 break
401 }
402 log.Info("An error occurred. Invoking next callback")
403 }
404 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500405 p.Unlock()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400406 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500407
Stephane Barbariea188d942018-10-16 16:43:04 -0400408 return result
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400409}