blob: be48dedff04d44d7b3a48429ca23da030091c715 [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 Barbarie1ab43272018-12-08 21:42:13 -050058 Root *root
59 Node *node
60 ParentNode *node
61 Path string
62 FullPath string
63 Exclusive bool
64 Callbacks map[CallbackType]map[string]*CallbackTuple
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040065}
66
Stephane Barbariedc5022d2018-11-19 15:21:44 -050067// NewProxy instantiates a new proxy to a specific location
Stephane Barbarie1ab43272018-12-08 21:42:13 -050068func NewProxy(root *root, node *node, parentNode *node, path string, fullPath string, exclusive bool) *Proxy {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050069 callbacks := make(map[CallbackType]map[string]*CallbackTuple)
Stephane Barbariea188d942018-10-16 16:43:04 -040070 if fullPath == "/" {
71 fullPath = ""
72 }
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040073 p := &Proxy{
Stephane Barbarie1ab43272018-12-08 21:42:13 -050074 Root: root,
75 Node: node,
76 ParentNode: parentNode,
77 Exclusive: exclusive,
78 Path: path,
79 FullPath: fullPath,
80 Callbacks: callbacks,
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040081 }
82 return p
83}
84
Stephane Barbariedc5022d2018-11-19 15:21:44 -050085// GetRoot returns the root attribute of the proxy
86func (p *Proxy) GetRoot() *root {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050087 return p.Root
88}
89
90// getPath returns the path attribute of the proxy
91func (p *Proxy) getPath() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050092 return p.Path
93}
94
95// getFullPath returns the full path attribute of the proxy
96func (p *Proxy) getFullPath() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050097 return p.FullPath
98}
99
100// getCallbacks returns the full list of callbacks associated to the proxy
101func (p *Proxy) getCallbacks(callbackType CallbackType) map[string]*CallbackTuple {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500102 if cb, exists := p.Callbacks[callbackType]; exists {
103 return cb
104 }
105 return nil
106}
107
108// getCallback returns a specific callback matching the type and function hash
109func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
Stephane Barbarie260a5632019-02-26 16:12:49 -0500110 p.Lock()
111 defer p.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500112 if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
113 return tuple
114 }
115 return nil
116}
117
118// setCallbacks applies a callbacks list to a type
119func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
120 p.Lock()
121 defer p.Unlock()
122 p.Callbacks[callbackType] = callbacks
123}
124
125// setCallback applies a callback to a type and hash value
126func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
127 p.Lock()
128 defer p.Unlock()
129 p.Callbacks[callbackType][funcHash] = tuple
130}
131
132// DeleteCallback removes a callback matching the type and hash
133func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
134 p.Lock()
135 defer p.Unlock()
136 delete(p.Callbacks[callbackType], funcHash)
137}
138
139// parseForControlledPath verifies if a proxy path matches a pattern
140// for locations that need to be access controlled.
Stephane Barbariea188d942018-10-16 16:43:04 -0400141func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500142 // TODO: Add other path prefixes that may need control
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500143 if strings.HasPrefix(path, "/devices") ||
144 strings.HasPrefix(path, "/logical_devices") ||
145 strings.HasPrefix(path, "/adapters") {
146
Stephane Barbariea188d942018-10-16 16:43:04 -0400147 split := strings.SplitN(path, "/", -1)
148 switch len(split) {
149 case 2:
150 controlled = false
151 pathLock = ""
152 break
153 case 3:
154 fallthrough
155 default:
156 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
157 controlled = true
158 }
159 }
160 return pathLock, controlled
161}
162
Stephane Barbarieaa467942019-02-06 14:09:44 -0500163// List will retrieve information from the data model at the specified path location
164// A list operation will force access to persistence storage
165func (p *Proxy) List(path string, depth int, deep bool, txid string) interface{} {
166 var effectivePath string
167 if path == "/" {
168 effectivePath = p.getFullPath()
169 } else {
170 effectivePath = p.getFullPath() + path
171 }
172
173 pathLock, controlled := p.parseForControlledPath(effectivePath)
174
175 log.Debugf("Path: %s, Effective: %s, PathLock: %s", path, effectivePath, pathLock)
176
177 pac := PAC().ReservePath(effectivePath, p, pathLock)
178 defer PAC().ReleasePath(pathLock)
179 pac.SetProxy(p)
180
181 rv := pac.List(path, depth, deep, txid, controlled)
182
183 return rv
184}
185
186
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500187// Get will retrieve information from the data model at the specified path location
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400188func (p *Proxy) Get(path string, depth int, deep bool, txid string) interface{} {
Stephane Barbariea188d942018-10-16 16:43:04 -0400189 var effectivePath string
190 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500191 effectivePath = p.getFullPath()
Stephane Barbariea188d942018-10-16 16:43:04 -0400192 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500193 effectivePath = p.getFullPath() + path
Stephane Barbariea188d942018-10-16 16:43:04 -0400194 }
195
196 pathLock, controlled := p.parseForControlledPath(effectivePath)
197
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500198 log.Debugf("Path: %s, Effective: %s, PathLock: %s", path, effectivePath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400199
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500200 pac := PAC().ReservePath(effectivePath, p, pathLock)
201 defer PAC().ReleasePath(pathLock)
202 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400203
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500204 rv := pac.Get(path, depth, deep, txid, controlled)
205
206 return rv
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400207}
208
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500209// Update will modify information in the data model at the specified location with the provided data
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400210func (p *Proxy) Update(path string, data interface{}, strict bool, txid string) interface{} {
211 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400212 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400213 return nil
214 }
215 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400216 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400217 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500218 fullPath = p.getPath()
219 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400220 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500221 fullPath = p.getPath() + path
Stephane Barbarie1039ec42019-02-04 10:43:16 -0500222 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400223 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400224
Stephane Barbariea188d942018-10-16 16:43:04 -0400225 pathLock, controlled := p.parseForControlledPath(effectivePath)
226
Stephane Barbarie260a5632019-02-26 16:12:49 -0500227 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s, Controlled: %b", path, effectivePath, fullPath, pathLock, controlled)
Stephane Barbariea188d942018-10-16 16:43:04 -0400228
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500229 pac := PAC().ReservePath(effectivePath, p, pathLock)
230 defer PAC().ReleasePath(pathLock)
231 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400232
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500233 return pac.Update(fullPath, data, strict, txid, controlled)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400234}
235
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500236// AddWithID will insert new data at specified location.
237// This method also allows the user to specify the ID of the data entry to ensure
238// that access control is active while inserting the information.
239func (p *Proxy) AddWithID(path string, id string, data interface{}, txid string) interface{} {
240 if !strings.HasPrefix(path, "/") {
241 log.Errorf("invalid path: %s", path)
242 return nil
243 }
244 var fullPath string
245 var effectivePath string
246 if path == "/" {
247 fullPath = p.getPath()
248 effectivePath = p.getFullPath()
249 } else {
250 fullPath = p.getPath() + path
251 effectivePath = p.getFullPath() + path + "/" + id
252 }
253
254 pathLock, controlled := p.parseForControlledPath(effectivePath)
255
256 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
257
258 pac := PAC().ReservePath(path, p, pathLock)
259 defer PAC().ReleasePath(pathLock)
260 pac.SetProxy(p)
261
262 return pac.Add(fullPath, data, txid, controlled)
263}
264
265// Add will insert new data at specified location.
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400266func (p *Proxy) Add(path string, data interface{}, txid string) interface{} {
267 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400268 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400269 return nil
270 }
271 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400272 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400273 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500274 fullPath = p.getPath()
275 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400276 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500277 fullPath = p.getPath() + path
278 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400279 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400280
281 pathLock, controlled := p.parseForControlledPath(effectivePath)
282
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500283 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400284
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500285 pac := PAC().ReservePath(path, p, pathLock)
286 defer PAC().ReleasePath(pathLock)
287 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400288
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500289 return pac.Add(fullPath, data, txid, controlled)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400290}
291
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500292// Remove will delete an entry at the specified location
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400293func (p *Proxy) Remove(path string, txid string) interface{} {
294 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400295 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400296 return nil
297 }
298 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400299 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400300 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500301 fullPath = p.getPath()
302 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400303 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500304 fullPath = p.getPath() + path
305 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400306 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400307
308 pathLock, controlled := p.parseForControlledPath(effectivePath)
309
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500310 log.Debugf("Path: %s, Effective: %s, Full: %s, PathLock: %s", path, effectivePath, fullPath, pathLock)
Stephane Barbariea188d942018-10-16 16:43:04 -0400311
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500312 pac := PAC().ReservePath(effectivePath, p, pathLock)
313 defer PAC().ReleasePath(pathLock)
314 pac.SetProxy(p)
Stephane Barbariea188d942018-10-16 16:43:04 -0400315
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500316 return pac.Remove(fullPath, txid, controlled)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400317}
318
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500319// OpenTransaction creates a new transaction branch to isolate operations made to the data model
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400320func (p *Proxy) OpenTransaction() *Transaction {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500321 txid := p.GetRoot().MakeTxBranch()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400322 return NewTransaction(p, txid)
323}
324
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500325// commitTransaction will apply and merge modifications made in the transaction branch to the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400326func (p *Proxy) commitTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500327 p.GetRoot().FoldTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400328}
329
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500330// cancelTransaction will terminate a transaction branch along will all changes within it
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400331func (p *Proxy) cancelTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500332 p.GetRoot().DeleteTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400333}
334
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500335// CallbackFunction is a type used to define callback functions
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400336type CallbackFunction func(args ...interface{}) interface{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500337
338// CallbackTuple holds the function and arguments details of a callback
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400339type CallbackTuple struct {
340 callback CallbackFunction
341 args []interface{}
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400342}
343
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500344// Execute will process the a callback with its provided arguments
345func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400346 args := []interface{}{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500347
348 for _, ta := range tuple.args {
349 args = append(args, ta)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400350 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500351
352 if contextArgs != nil {
353 for _, ca := range contextArgs {
354 args = append(args, ca)
355 }
356 }
357
Stephane Barbarie126101e2018-10-11 16:18:48 -0400358 return tuple.callback(args...)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400359}
360
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500361// RegisterCallback associates a callback to the proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400362func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500363 if p.getCallbacks(callbackType) == nil {
364 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400365 }
366 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
367 log.Debugf("value of function: %s", funcName)
368 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
369
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500370 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400371}
372
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500373// UnregisterCallback removes references to a callback within a proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400374func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500375 if p.getCallbacks(callbackType) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400376 log.Errorf("no such callback type - %s", callbackType.String())
377 return
378 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500379
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400380 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400381 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500382
383 log.Debugf("value of function: %s", funcName)
384
385 if p.getCallback(callbackType, funcHash) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400386 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
387 return
388 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500389
390 p.DeleteCallback(callbackType, funcHash)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400391}
392
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500393func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400394 defer func() {
395 if r := recover(); r != nil {
396 errStr := fmt.Sprintf("callback error occurred: %+v", r)
397 err = errors.New(errStr)
398 log.Error(errStr)
399 }
400 }()
401
402 result = callback.Execute(context)
403
404 return result, err
405}
406
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500407// InvokeCallbacks executes all callbacks associated to a specific type
Stephane Barbarie126101e2018-10-11 16:18:48 -0400408func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400409 callbackType := args[0].(CallbackType)
Stephane Barbarie126101e2018-10-11 16:18:48 -0400410 proceedOnError := args[1].(bool)
411 context := args[2:]
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400412
413 var err error
414
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500415 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
416 p.Lock()
417 for _, callback := range callbacks {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400418 if result, err = p.invoke(callback, context); err != nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400419 if !proceedOnError {
420 log.Info("An error occurred. Stopping callback invocation")
421 break
422 }
423 log.Info("An error occurred. Invoking next callback")
424 }
425 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500426 p.Unlock()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400427 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500428
Stephane Barbariea188d942018-10-16 16:43:04 -0400429 return result
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400430}