blob: b5378fe670f2827a974470a49482c19940ab34a7 [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 Barbarieef6650d2019-07-18 12:15:09 -040020 "context"
Stephane Barbarie694e2b92018-09-07 12:17:36 -040021 "crypto/md5"
Stephane Barbarie694e2b92018-09-07 12:17:36 -040022 "errors"
khenaidoob9203542018-09-17 22:56:37 -040023 "fmt"
Stephane Barbarieef6650d2019-07-18 12:15:09 -040024 "github.com/google/uuid"
Scott Baker807addd2019-10-24 15:16:21 -070025 "github.com/opencord/voltha-lib-go/v2/pkg/log"
khenaidoob9203542018-09-17 22:56:37 -040026 "reflect"
Stephane Barbarie694e2b92018-09-07 12:17:36 -040027 "runtime"
khenaidoob9203542018-09-17 22:56:37 -040028 "strings"
Stephane Barbariea188d942018-10-16 16:43:04 -040029 "sync"
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040030)
31
Stephane Barbariedc5022d2018-11-19 15:21:44 -050032// OperationContext holds details on the information used during an operation
Stephane Barbarieec0919b2018-09-05 14:14:29 -040033type OperationContext struct {
34 Path string
35 Data interface{}
36 FieldName string
37 ChildKey string
38}
39
Stephane Barbariedc5022d2018-11-19 15:21:44 -050040// NewOperationContext instantiates a new OperationContext structure
Stephane Barbarieec0919b2018-09-05 14:14:29 -040041func NewOperationContext(path string, data interface{}, fieldName string, childKey string) *OperationContext {
42 oc := &OperationContext{
43 Path: path,
44 Data: data,
45 FieldName: fieldName,
46 ChildKey: childKey,
47 }
48 return oc
49}
50
Stephane Barbariedc5022d2018-11-19 15:21:44 -050051// Update applies new data to the context structure
Stephane Barbarieec0919b2018-09-05 14:14:29 -040052func (oc *OperationContext) Update(data interface{}) *OperationContext {
53 oc.Data = data
54 return oc
55}
56
Stephane Barbariedc5022d2018-11-19 15:21:44 -050057// Proxy holds the information for a specific location with the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040058type Proxy struct {
Stephane Barbarieef6650d2019-07-18 12:15:09 -040059 mutex sync.RWMutex
Stephane Barbariec53a2752019-03-08 17:50:10 -050060 Root *root
61 Node *node
62 ParentNode *node
63 Path string
64 FullPath string
65 Exclusive bool
66 Callbacks map[CallbackType]map[string]*CallbackTuple
Stephane Barbarieef6650d2019-07-18 12:15:09 -040067 operation ProxyOperation
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040068}
69
Stephane Barbariedc5022d2018-11-19 15:21:44 -050070// NewProxy instantiates a new proxy to a specific location
Stephane Barbarie1ab43272018-12-08 21:42:13 -050071func NewProxy(root *root, node *node, parentNode *node, path string, fullPath string, exclusive bool) *Proxy {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050072 callbacks := make(map[CallbackType]map[string]*CallbackTuple)
Stephane Barbariea188d942018-10-16 16:43:04 -040073 if fullPath == "/" {
74 fullPath = ""
75 }
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040076 p := &Proxy{
Stephane Barbariec53a2752019-03-08 17:50:10 -050077 Root: root,
78 Node: node,
79 ParentNode: parentNode,
80 Exclusive: exclusive,
81 Path: path,
82 FullPath: fullPath,
83 Callbacks: callbacks,
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040084 }
85 return p
86}
87
Stephane Barbariedc5022d2018-11-19 15:21:44 -050088// GetRoot returns the root attribute of the proxy
89func (p *Proxy) GetRoot() *root {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050090 return p.Root
91}
92
93// getPath returns the path attribute of the proxy
94func (p *Proxy) getPath() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050095 return p.Path
96}
97
98// getFullPath returns the full path attribute of the proxy
99func (p *Proxy) getFullPath() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500100 return p.FullPath
101}
102
103// getCallbacks returns the full list of callbacks associated to the proxy
104func (p *Proxy) getCallbacks(callbackType CallbackType) map[string]*CallbackTuple {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400105 p.mutex.RLock()
106 defer p.mutex.RUnlock()
107
Stephane Barbariec92d1072019-06-07 16:21:49 -0400108 if p != nil {
109 if cb, exists := p.Callbacks[callbackType]; exists {
110 return cb
111 }
112 } else {
113 log.Debugw("proxy-is-nil", log.Fields{"callback-type": callbackType.String()})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500114 }
115 return nil
116}
117
118// getCallback returns a specific callback matching the type and function hash
119func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400120 p.mutex.Lock()
121 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500122 if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
123 return tuple
124 }
125 return nil
126}
127
128// setCallbacks applies a callbacks list to a type
129func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400130 p.mutex.Lock()
131 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500132 p.Callbacks[callbackType] = callbacks
133}
134
135// setCallback applies a callback to a type and hash value
136func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400137 p.mutex.Lock()
138 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500139 p.Callbacks[callbackType][funcHash] = tuple
140}
141
142// DeleteCallback removes a callback matching the type and hash
143func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400144 p.mutex.Lock()
145 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500146 delete(p.Callbacks[callbackType], funcHash)
147}
148
Stephane Barbariec53a2752019-03-08 17:50:10 -0500149// CallbackType is an enumerated value to express when a callback should be executed
150type ProxyOperation uint8
151
152// Enumerated list of callback types
153const (
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400154 PROXY_NONE ProxyOperation = iota
155 PROXY_GET
Stephane Barbariec53a2752019-03-08 17:50:10 -0500156 PROXY_LIST
157 PROXY_ADD
158 PROXY_UPDATE
159 PROXY_REMOVE
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400160 PROXY_CREATE
Stephane Barbariec92d1072019-06-07 16:21:49 -0400161 PROXY_WATCH
Stephane Barbariec53a2752019-03-08 17:50:10 -0500162)
163
Stephane Barbarie802aca42019-05-21 12:19:28 -0400164var proxyOperationTypes = []string{
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400165 "PROXY_NONE",
Stephane Barbarie802aca42019-05-21 12:19:28 -0400166 "PROXY_GET",
167 "PROXY_LIST",
168 "PROXY_ADD",
169 "PROXY_UPDATE",
170 "PROXY_REMOVE",
171 "PROXY_CREATE",
Stephane Barbariec92d1072019-06-07 16:21:49 -0400172 "PROXY_WATCH",
Stephane Barbarie802aca42019-05-21 12:19:28 -0400173}
174
175func (t ProxyOperation) String() string {
176 return proxyOperationTypes[t]
177}
178
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400179func (p *Proxy) GetOperation() ProxyOperation {
180 p.mutex.RLock()
181 defer p.mutex.RUnlock()
182 return p.operation
183}
184
185func (p *Proxy) SetOperation(operation ProxyOperation) {
186 p.mutex.Lock()
187 defer p.mutex.Unlock()
188 p.operation = operation
189}
190
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500191// parseForControlledPath verifies if a proxy path matches a pattern
192// for locations that need to be access controlled.
Stephane Barbariea188d942018-10-16 16:43:04 -0400193func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500194 // TODO: Add other path prefixes that may need control
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500195 if strings.HasPrefix(path, "/devices") ||
196 strings.HasPrefix(path, "/logical_devices") ||
197 strings.HasPrefix(path, "/adapters") {
198
Stephane Barbariea188d942018-10-16 16:43:04 -0400199 split := strings.SplitN(path, "/", -1)
200 switch len(split) {
201 case 2:
202 controlled = false
203 pathLock = ""
204 break
205 case 3:
206 fallthrough
207 default:
208 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
209 controlled = true
210 }
211 }
212 return pathLock, controlled
213}
214
Stephane Barbarieaa467942019-02-06 14:09:44 -0500215// List will retrieve information from the data model at the specified path location
216// A list operation will force access to persistence storage
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400217func (p *Proxy) List(ctx context.Context, path string, depth int, deep bool, txid string) interface{} {
Stephane Barbarieaa467942019-02-06 14:09:44 -0500218 var effectivePath string
219 if path == "/" {
220 effectivePath = p.getFullPath()
221 } else {
222 effectivePath = p.getFullPath() + path
223 }
224
225 pathLock, controlled := p.parseForControlledPath(effectivePath)
226
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400227 p.SetOperation(PROXY_LIST)
228 defer p.SetOperation(PROXY_NONE)
229
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400230 log.Debugw("proxy-list", log.Fields{
231 "path": path,
232 "effective": effectivePath,
233 "pathLock": pathLock,
234 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400235 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400236 })
Stephane Barbarieaa467942019-02-06 14:09:44 -0500237
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400238 rv := p.GetRoot().List(ctx, path, "", depth, deep, txid)
Stephane Barbarieaa467942019-02-06 14:09:44 -0500239
240 return rv
241}
242
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500243// Get will retrieve information from the data model at the specified path location
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400244func (p *Proxy) Get(ctx context.Context, path string, depth int, deep bool, txid string) interface{} {
Stephane Barbariea188d942018-10-16 16:43:04 -0400245 var effectivePath string
246 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500247 effectivePath = p.getFullPath()
Stephane Barbariea188d942018-10-16 16:43:04 -0400248 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500249 effectivePath = p.getFullPath() + path
Stephane Barbariea188d942018-10-16 16:43:04 -0400250 }
251
252 pathLock, controlled := p.parseForControlledPath(effectivePath)
253
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400254 p.SetOperation(PROXY_GET)
255 defer p.SetOperation(PROXY_NONE)
256
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400257 log.Debugw("proxy-get", log.Fields{
258 "path": path,
259 "effective": effectivePath,
260 "pathLock": pathLock,
261 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400262 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400263 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400264
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400265 rv := p.GetRoot().Get(ctx, path, "", depth, deep, txid)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500266
267 return rv
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400268}
269
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500270// Update will modify information in the data model at the specified location with the provided data
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400271func (p *Proxy) Update(ctx context.Context, path string, data interface{}, strict bool, txid string) interface{} {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400272 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400273 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400274 return nil
275 }
276 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400277 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400278 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500279 fullPath = p.getPath()
280 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400281 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500282 fullPath = p.getPath() + path
Stephane Barbarie1039ec42019-02-04 10:43:16 -0500283 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400284 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400285
Stephane Barbariea188d942018-10-16 16:43:04 -0400286 pathLock, controlled := p.parseForControlledPath(effectivePath)
287
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400288 p.SetOperation(PROXY_UPDATE)
289 defer p.SetOperation(PROXY_NONE)
290
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400291 log.Debugw("proxy-update", log.Fields{
292 "path": path,
293 "effective": effectivePath,
294 "full": fullPath,
295 "pathLock": pathLock,
296 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400297 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400298 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400299
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400300 if p.GetRoot().KvStore != nil {
301 p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
302 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
303 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500304
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400305 result := p.GetRoot().Update(ctx, fullPath, data, strict, txid, nil)
Stephane Barbariec53a2752019-03-08 17:50:10 -0500306
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400307 if result != nil {
308 return result.GetData()
309 }
310
311 return nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400312}
313
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500314// AddWithID will insert new data at specified location.
315// This method also allows the user to specify the ID of the data entry to ensure
316// that access control is active while inserting the information.
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400317func (p *Proxy) AddWithID(ctx context.Context, path string, id string, data interface{}, txid string) interface{} {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500318 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 + "/" + id
330 }
331
332 pathLock, controlled := p.parseForControlledPath(effectivePath)
333
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400334 p.SetOperation(PROXY_ADD)
335 defer p.SetOperation(PROXY_NONE)
336
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400337 log.Debugw("proxy-add-with-id", log.Fields{
338 "path": path,
339 "effective": effectivePath,
340 "full": fullPath,
341 "pathLock": pathLock,
342 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400343 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400344 })
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500345
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400346 if p.GetRoot().KvStore != nil {
347 p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
348 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
349 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500350
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400351 result := p.GetRoot().Add(ctx, fullPath, data, txid, nil)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400352
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400353 if result != nil {
354 return result.GetData()
355 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500356
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400357 return nil
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500358}
359
360// Add will insert new data at specified location.
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400361func (p *Proxy) Add(ctx context.Context, path string, data interface{}, txid string) interface{} {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400362 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400363 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400364 return nil
365 }
366 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400367 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400368 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500369 fullPath = p.getPath()
370 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400371 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500372 fullPath = p.getPath() + path
373 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400374 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400375
376 pathLock, controlled := p.parseForControlledPath(effectivePath)
377
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400378 p.SetOperation(PROXY_ADD)
379 defer p.SetOperation(PROXY_NONE)
380
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400381 log.Debugw("proxy-add", log.Fields{
382 "path": path,
383 "effective": effectivePath,
384 "full": fullPath,
385 "pathLock": pathLock,
386 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400387 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400388 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400389
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400390 if p.GetRoot().KvStore != nil {
391 p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
392 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
393 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500394
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400395 result := p.GetRoot().Add(ctx, fullPath, data, txid, nil)
Stephane Barbariea188d942018-10-16 16:43:04 -0400396
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400397 if result != nil {
398 return result.GetData()
399 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500400
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400401 return nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400402}
403
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500404// Remove will delete an entry at the specified location
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400405func (p *Proxy) Remove(ctx context.Context, path string, txid string) interface{} {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400406 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400407 log.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400408 return nil
409 }
410 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400411 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400412 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500413 fullPath = p.getPath()
414 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400415 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500416 fullPath = p.getPath() + path
417 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400418 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400419
420 pathLock, controlled := p.parseForControlledPath(effectivePath)
421
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400422 p.SetOperation(PROXY_REMOVE)
423 defer p.SetOperation(PROXY_NONE)
424
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400425 log.Debugw("proxy-remove", log.Fields{
426 "path": path,
427 "effective": effectivePath,
428 "full": fullPath,
429 "pathLock": pathLock,
430 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400431 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400432 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400433
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400434 if p.GetRoot().KvStore != nil {
435 p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
436 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
437 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500438
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400439 result := p.GetRoot().Remove(ctx, fullPath, txid, nil)
Stephane Barbariea188d942018-10-16 16:43:04 -0400440
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400441 if result != nil {
442 return result.GetData()
443 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500444
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400445 return nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400446}
447
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400448// CreateProxy to interact with specific path directly
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400449func (p *Proxy) CreateProxy(ctx context.Context, path string, exclusive bool) *Proxy {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400450 if !strings.HasPrefix(path, "/") {
451 log.Errorf("invalid path: %s", path)
452 return nil
453 }
454
455 var fullPath string
456 var effectivePath string
457 if path == "/" {
458 fullPath = p.getPath()
459 effectivePath = p.getFullPath()
460 } else {
461 fullPath = p.getPath() + path
462 effectivePath = p.getFullPath() + path
463 }
464
465 pathLock, controlled := p.parseForControlledPath(effectivePath)
466
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400467 p.SetOperation(PROXY_CREATE)
468 defer p.SetOperation(PROXY_NONE)
469
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400470 log.Debugw("proxy-create", log.Fields{
471 "path": path,
472 "effective": effectivePath,
473 "full": fullPath,
474 "pathLock": pathLock,
475 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400476 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400477 })
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400478
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400479 if p.GetRoot().KvStore != nil {
480 p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL)
481 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
482 }
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400483
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400484 return p.GetRoot().CreateProxy(ctx, fullPath, exclusive)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400485}
486
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500487// OpenTransaction creates a new transaction branch to isolate operations made to the data model
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400488func (p *Proxy) OpenTransaction() *Transaction {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500489 txid := p.GetRoot().MakeTxBranch()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400490 return NewTransaction(p, txid)
491}
492
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500493// commitTransaction will apply and merge modifications made in the transaction branch to the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400494func (p *Proxy) commitTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500495 p.GetRoot().FoldTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400496}
497
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500498// cancelTransaction will terminate a transaction branch along will all changes within it
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400499func (p *Proxy) cancelTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500500 p.GetRoot().DeleteTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400501}
502
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500503// CallbackFunction is a type used to define callback functions
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400504type CallbackFunction func(args ...interface{}) interface{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500505
506// CallbackTuple holds the function and arguments details of a callback
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400507type CallbackTuple struct {
508 callback CallbackFunction
509 args []interface{}
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400510}
511
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500512// Execute will process the a callback with its provided arguments
513func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400514 args := []interface{}{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500515
516 for _, ta := range tuple.args {
517 args = append(args, ta)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400518 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500519
520 if contextArgs != nil {
521 for _, ca := range contextArgs {
522 args = append(args, ca)
523 }
524 }
525
Stephane Barbarie126101e2018-10-11 16:18:48 -0400526 return tuple.callback(args...)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400527}
528
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500529// RegisterCallback associates a callback to the proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400530func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500531 if p.getCallbacks(callbackType) == nil {
532 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400533 }
534 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
535 log.Debugf("value of function: %s", funcName)
536 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
537
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500538 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400539}
540
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500541// UnregisterCallback removes references to a callback within a proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400542func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500543 if p.getCallbacks(callbackType) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400544 log.Errorf("no such callback type - %s", callbackType.String())
545 return
546 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500547
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400548 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400549 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500550
551 log.Debugf("value of function: %s", funcName)
552
553 if p.getCallback(callbackType, funcHash) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400554 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
555 return
556 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500557
558 p.DeleteCallback(callbackType, funcHash)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400559}
560
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500561func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400562 defer func() {
563 if r := recover(); r != nil {
564 errStr := fmt.Sprintf("callback error occurred: %+v", r)
565 err = errors.New(errStr)
566 log.Error(errStr)
567 }
568 }()
569
570 result = callback.Execute(context)
571
572 return result, err
573}
574
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500575// InvokeCallbacks executes all callbacks associated to a specific type
Stephane Barbarie126101e2018-10-11 16:18:48 -0400576func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400577 callbackType := args[0].(CallbackType)
Stephane Barbarie126101e2018-10-11 16:18:48 -0400578 proceedOnError := args[1].(bool)
579 context := args[2:]
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400580
581 var err error
582
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500583 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400584 p.mutex.Lock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500585 for _, callback := range callbacks {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400586 if result, err = p.invoke(callback, context); err != nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400587 if !proceedOnError {
588 log.Info("An error occurred. Stopping callback invocation")
589 break
590 }
591 log.Info("An error occurred. Invoking next callback")
592 }
593 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400594 p.mutex.Unlock()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400595 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500596
Stephane Barbariea188d942018-10-16 16:43:04 -0400597 return result
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400598}