blob: a7eedda46107d116105fc550f201d37f48b770a3 [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
Thomas Lee Se5a44012019-11-07 20:32:24 +0530217func (p *Proxy) List(ctx context.Context, path string, depth int, deep bool, txid string) (interface{}, error) {
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 })
Thomas Lee Se5a44012019-11-07 20:32:24 +0530237 return p.GetRoot().List(ctx, path, "", depth, deep, txid)
Stephane Barbarieaa467942019-02-06 14:09:44 -0500238}
239
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500240// Get will retrieve information from the data model at the specified path location
Thomas Lee Se5a44012019-11-07 20:32:24 +0530241func (p *Proxy) Get(ctx context.Context, path string, depth int, deep bool, txid string) (interface{}, error) {
Stephane Barbariea188d942018-10-16 16:43:04 -0400242 var effectivePath string
243 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500244 effectivePath = p.getFullPath()
Stephane Barbariea188d942018-10-16 16:43:04 -0400245 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500246 effectivePath = p.getFullPath() + path
Stephane Barbariea188d942018-10-16 16:43:04 -0400247 }
248
249 pathLock, controlled := p.parseForControlledPath(effectivePath)
250
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400251 p.SetOperation(PROXY_GET)
252 defer p.SetOperation(PROXY_NONE)
253
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400254 log.Debugw("proxy-get", log.Fields{
255 "path": path,
256 "effective": effectivePath,
257 "pathLock": pathLock,
258 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400259 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400260 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400261
Thomas Lee Se5a44012019-11-07 20:32:24 +0530262 return p.GetRoot().Get(ctx, path, "", depth, deep, txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400263}
264
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500265// Update will modify information in the data model at the specified location with the provided data
Thomas Lee Se5a44012019-11-07 20:32:24 +0530266func (p *Proxy) Update(ctx context.Context, path string, data interface{}, strict bool, txid string) (interface{}, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400267 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400268 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530269 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400270 }
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
Stephane Barbarie1039ec42019-02-04 10:43:16 -0500278 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400279 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400280
Stephane Barbariea188d942018-10-16 16:43:04 -0400281 pathLock, controlled := p.parseForControlledPath(effectivePath)
282
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400283 p.SetOperation(PROXY_UPDATE)
284 defer p.SetOperation(PROXY_NONE)
285
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400286 log.Debugw("proxy-update", log.Fields{
287 "path": path,
288 "effective": effectivePath,
289 "full": fullPath,
290 "pathLock": pathLock,
291 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400292 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400293 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400294
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400295 if p.GetRoot().KvStore != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530296 if _, err := p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
297 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
298 return nil, err
299 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400300 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
301 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500302
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400303 result := p.GetRoot().Update(ctx, fullPath, data, strict, txid, nil)
Stephane Barbariec53a2752019-03-08 17:50:10 -0500304
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400305 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530306 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400307 }
308
Thomas Lee Se5a44012019-11-07 20:32:24 +0530309 return nil, nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400310}
311
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500312// AddWithID will insert new data at specified location.
313// This method also allows the user to specify the ID of the data entry to ensure
314// that access control is active while inserting the information.
Thomas Lee Se5a44012019-11-07 20:32:24 +0530315func (p *Proxy) AddWithID(ctx context.Context, path string, id string, data interface{}, txid string) (interface{}, error) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500316 if !strings.HasPrefix(path, "/") {
317 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530318 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500319 }
320 var fullPath string
321 var effectivePath string
322 if path == "/" {
323 fullPath = p.getPath()
324 effectivePath = p.getFullPath()
325 } else {
326 fullPath = p.getPath() + path
327 effectivePath = p.getFullPath() + path + "/" + id
328 }
329
330 pathLock, controlled := p.parseForControlledPath(effectivePath)
331
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400332 p.SetOperation(PROXY_ADD)
333 defer p.SetOperation(PROXY_NONE)
334
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400335 log.Debugw("proxy-add-with-id", log.Fields{
336 "path": path,
337 "effective": effectivePath,
338 "full": fullPath,
339 "pathLock": pathLock,
340 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400341 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400342 })
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500343
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400344 if p.GetRoot().KvStore != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530345 if _, err := p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
346 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
347 return nil, err
348 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400349 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
350 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500351
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400352 result := p.GetRoot().Add(ctx, fullPath, data, txid, nil)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400353
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400354 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530355 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400356 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500357
Thomas Lee Se5a44012019-11-07 20:32:24 +0530358 return nil, nil
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500359}
360
361// Add will insert new data at specified location.
Thomas Lee Se5a44012019-11-07 20:32:24 +0530362func (p *Proxy) Add(ctx context.Context, path string, data interface{}, txid string) (interface{}, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400363 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400364 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530365 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400366 }
367 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400368 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400369 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500370 fullPath = p.getPath()
371 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400372 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500373 fullPath = p.getPath() + path
374 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400375 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400376
377 pathLock, controlled := p.parseForControlledPath(effectivePath)
378
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400379 p.SetOperation(PROXY_ADD)
380 defer p.SetOperation(PROXY_NONE)
381
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400382 log.Debugw("proxy-add", log.Fields{
383 "path": path,
384 "effective": effectivePath,
385 "full": fullPath,
386 "pathLock": pathLock,
387 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400388 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400389 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400390
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400391 if p.GetRoot().KvStore != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530392 if _, err := p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
393 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
394 return nil, err
395 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400396 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
397 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500398
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400399 result := p.GetRoot().Add(ctx, fullPath, data, txid, nil)
Stephane Barbariea188d942018-10-16 16:43:04 -0400400
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400401 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530402 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400403 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500404
Thomas Lee Se5a44012019-11-07 20:32:24 +0530405 return nil, nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400406}
407
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500408// Remove will delete an entry at the specified location
Thomas Lee Se5a44012019-11-07 20:32:24 +0530409func (p *Proxy) Remove(ctx context.Context, path string, txid string) (interface{}, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400410 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400411 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530412 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400413 }
414 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400415 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400416 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500417 fullPath = p.getPath()
418 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400419 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500420 fullPath = p.getPath() + path
421 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400422 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400423
424 pathLock, controlled := p.parseForControlledPath(effectivePath)
425
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400426 p.SetOperation(PROXY_REMOVE)
427 defer p.SetOperation(PROXY_NONE)
428
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400429 log.Debugw("proxy-remove", log.Fields{
430 "path": path,
431 "effective": effectivePath,
432 "full": fullPath,
433 "pathLock": pathLock,
434 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400435 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400436 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400437
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400438 if p.GetRoot().KvStore != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530439 if _, err := p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
440 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
441 return nil, err
442 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400443 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
444 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500445
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400446 result := p.GetRoot().Remove(ctx, fullPath, txid, nil)
Stephane Barbariea188d942018-10-16 16:43:04 -0400447
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400448 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530449 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400450 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500451
Thomas Lee Se5a44012019-11-07 20:32:24 +0530452 return nil, nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400453}
454
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400455// CreateProxy to interact with specific path directly
Thomas Lee Se5a44012019-11-07 20:32:24 +0530456func (p *Proxy) CreateProxy(ctx context.Context, path string, exclusive bool) (*Proxy, error) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400457 if !strings.HasPrefix(path, "/") {
458 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530459 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400460 }
461
462 var fullPath string
463 var effectivePath string
464 if path == "/" {
465 fullPath = p.getPath()
466 effectivePath = p.getFullPath()
467 } else {
468 fullPath = p.getPath() + path
469 effectivePath = p.getFullPath() + path
470 }
471
472 pathLock, controlled := p.parseForControlledPath(effectivePath)
473
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400474 p.SetOperation(PROXY_CREATE)
475 defer p.SetOperation(PROXY_NONE)
476
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400477 log.Debugw("proxy-create", log.Fields{
478 "path": path,
479 "effective": effectivePath,
480 "full": fullPath,
481 "pathLock": pathLock,
482 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400483 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400484 })
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400485
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400486 if p.GetRoot().KvStore != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530487 if _, err := p.GetRoot().KvStore.Client.Reserve(pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
488 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
489 return nil, err
490 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400491 defer p.GetRoot().KvStore.Client.ReleaseReservation(pathLock + "_")
492 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400493 return p.GetRoot().CreateProxy(ctx, fullPath, exclusive)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400494}
495
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500496// OpenTransaction creates a new transaction branch to isolate operations made to the data model
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400497func (p *Proxy) OpenTransaction() *Transaction {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500498 txid := p.GetRoot().MakeTxBranch()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400499 return NewTransaction(p, txid)
500}
501
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500502// commitTransaction will apply and merge modifications made in the transaction branch to the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400503func (p *Proxy) commitTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500504 p.GetRoot().FoldTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400505}
506
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500507// cancelTransaction will terminate a transaction branch along will all changes within it
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400508func (p *Proxy) cancelTransaction(txid string) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500509 p.GetRoot().DeleteTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400510}
511
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500512// CallbackFunction is a type used to define callback functions
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400513type CallbackFunction func(args ...interface{}) interface{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500514
515// CallbackTuple holds the function and arguments details of a callback
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400516type CallbackTuple struct {
517 callback CallbackFunction
518 args []interface{}
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400519}
520
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500521// Execute will process the a callback with its provided arguments
522func (tuple *CallbackTuple) Execute(contextArgs []interface{}) interface{} {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400523 args := []interface{}{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500524
525 for _, ta := range tuple.args {
526 args = append(args, ta)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400527 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500528
529 if contextArgs != nil {
530 for _, ca := range contextArgs {
531 args = append(args, ca)
532 }
533 }
534
Stephane Barbarie126101e2018-10-11 16:18:48 -0400535 return tuple.callback(args...)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400536}
537
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500538// RegisterCallback associates a callback to the proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400539func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500540 if p.getCallbacks(callbackType) == nil {
541 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400542 }
543 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
544 log.Debugf("value of function: %s", funcName)
545 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
546
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500547 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400548}
549
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500550// UnregisterCallback removes references to a callback within a proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400551func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500552 if p.getCallbacks(callbackType) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400553 log.Errorf("no such callback type - %s", callbackType.String())
554 return
555 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500556
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400557 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400558 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500559
560 log.Debugf("value of function: %s", funcName)
561
562 if p.getCallback(callbackType, funcHash) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400563 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
564 return
565 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500566
567 p.DeleteCallback(callbackType, funcHash)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400568}
569
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500570func (p *Proxy) invoke(callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400571 defer func() {
572 if r := recover(); r != nil {
573 errStr := fmt.Sprintf("callback error occurred: %+v", r)
574 err = errors.New(errStr)
575 log.Error(errStr)
576 }
577 }()
578
579 result = callback.Execute(context)
580
581 return result, err
582}
583
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500584// InvokeCallbacks executes all callbacks associated to a specific type
Stephane Barbarie126101e2018-10-11 16:18:48 -0400585func (p *Proxy) InvokeCallbacks(args ...interface{}) (result interface{}) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400586 callbackType := args[0].(CallbackType)
Stephane Barbarie126101e2018-10-11 16:18:48 -0400587 proceedOnError := args[1].(bool)
588 context := args[2:]
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400589
590 var err error
591
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500592 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400593 p.mutex.Lock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500594 for _, callback := range callbacks {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400595 if result, err = p.invoke(callback, context); err != nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400596 if !proceedOnError {
597 log.Info("An error occurred. Stopping callback invocation")
598 break
599 }
600 log.Info("An error occurred. Invoking next callback")
601 }
602 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400603 p.mutex.Unlock()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400604 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500605
Stephane Barbariea188d942018-10-16 16:43:04 -0400606 return result
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400607}