blob: 3ffc9ff470ea889219e20b44f09db0ccf0e9cf8b [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"
khenaidoob9203542018-09-17 22:56:37 -040024 "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"
npujar9a30c702019-11-14 17:06:39 +053028
29 "github.com/google/uuid"
serkant.uluderya2ae470f2020-01-21 11:13:09 -080030 "github.com/opencord/voltha-lib-go/v3/pkg/log"
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040031)
32
Stephane Barbariedc5022d2018-11-19 15:21:44 -050033// OperationContext holds details on the information used during an operation
Stephane Barbarieec0919b2018-09-05 14:14:29 -040034type OperationContext struct {
35 Path string
36 Data interface{}
37 FieldName string
38 ChildKey string
39}
40
Stephane Barbariedc5022d2018-11-19 15:21:44 -050041// NewOperationContext instantiates a new OperationContext structure
Stephane Barbarieec0919b2018-09-05 14:14:29 -040042func NewOperationContext(path string, data interface{}, fieldName string, childKey string) *OperationContext {
43 oc := &OperationContext{
44 Path: path,
45 Data: data,
46 FieldName: fieldName,
47 ChildKey: childKey,
48 }
49 return oc
50}
51
Stephane Barbariedc5022d2018-11-19 15:21:44 -050052// Update applies new data to the context structure
Stephane Barbarieec0919b2018-09-05 14:14:29 -040053func (oc *OperationContext) Update(data interface{}) *OperationContext {
54 oc.Data = data
55 return oc
56}
57
Stephane Barbariedc5022d2018-11-19 15:21:44 -050058// Proxy holds the information for a specific location with the data model
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040059type Proxy struct {
Stephane Barbarieef6650d2019-07-18 12:15:09 -040060 mutex sync.RWMutex
Stephane Barbariec53a2752019-03-08 17:50:10 -050061 Root *root
62 Node *node
63 ParentNode *node
64 Path string
65 FullPath string
66 Exclusive bool
67 Callbacks map[CallbackType]map[string]*CallbackTuple
Stephane Barbarieef6650d2019-07-18 12:15:09 -040068 operation ProxyOperation
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040069}
70
Stephane Barbariedc5022d2018-11-19 15:21:44 -050071// NewProxy instantiates a new proxy to a specific location
Stephane Barbarie1ab43272018-12-08 21:42:13 -050072func NewProxy(root *root, node *node, parentNode *node, path string, fullPath string, exclusive bool) *Proxy {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050073 callbacks := make(map[CallbackType]map[string]*CallbackTuple)
Stephane Barbariea188d942018-10-16 16:43:04 -040074 if fullPath == "/" {
75 fullPath = ""
76 }
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040077 p := &Proxy{
Stephane Barbariec53a2752019-03-08 17:50:10 -050078 Root: root,
79 Node: node,
80 ParentNode: parentNode,
81 Exclusive: exclusive,
82 Path: path,
83 FullPath: fullPath,
84 Callbacks: callbacks,
Stephane Barbarie4a2564d2018-07-26 11:02:58 -040085 }
86 return p
87}
88
npujar9a30c702019-11-14 17:06:39 +053089// getRoot returns the root attribute of the proxy
90func (p *Proxy) getRoot() *root {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050091 return p.Root
92}
93
94// getPath returns the path attribute of the proxy
95func (p *Proxy) getPath() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -050096 return p.Path
97}
98
99// getFullPath returns the full path attribute of the proxy
100func (p *Proxy) getFullPath() string {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500101 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 {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400106 p.mutex.RLock()
107 defer p.mutex.RUnlock()
108
Stephane Barbariec92d1072019-06-07 16:21:49 -0400109 if p != nil {
110 if cb, exists := p.Callbacks[callbackType]; exists {
111 return cb
112 }
113 } else {
114 log.Debugw("proxy-is-nil", log.Fields{"callback-type": callbackType.String()})
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500115 }
116 return nil
117}
118
119// getCallback returns a specific callback matching the type and function hash
120func (p *Proxy) getCallback(callbackType CallbackType, funcHash string) *CallbackTuple {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400121 p.mutex.Lock()
122 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500123 if tuple, exists := p.Callbacks[callbackType][funcHash]; exists {
124 return tuple
125 }
126 return nil
127}
128
129// setCallbacks applies a callbacks list to a type
130func (p *Proxy) setCallbacks(callbackType CallbackType, callbacks map[string]*CallbackTuple) {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400131 p.mutex.Lock()
132 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500133 p.Callbacks[callbackType] = callbacks
134}
135
136// setCallback applies a callback to a type and hash value
137func (p *Proxy) setCallback(callbackType CallbackType, funcHash string, tuple *CallbackTuple) {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400138 p.mutex.Lock()
139 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500140 p.Callbacks[callbackType][funcHash] = tuple
141}
142
143// DeleteCallback removes a callback matching the type and hash
144func (p *Proxy) DeleteCallback(callbackType CallbackType, funcHash string) {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400145 p.mutex.Lock()
146 defer p.mutex.Unlock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500147 delete(p.Callbacks[callbackType], funcHash)
148}
149
npujar9a30c702019-11-14 17:06:39 +0530150// ProxyOperation callbackType is an enumerated value to express when a callback should be executed
Stephane Barbariec53a2752019-03-08 17:50:10 -0500151type ProxyOperation uint8
152
153// Enumerated list of callback types
154const (
npujar9a30c702019-11-14 17:06:39 +0530155 ProxyNone ProxyOperation = iota
156 ProxyGet
157 ProxyList
158 ProxyAdd
159 ProxyUpdate
160 ProxyRemove
161 ProxyCreate
162 ProxyWatch
Stephane Barbariec53a2752019-03-08 17:50:10 -0500163)
164
Stephane Barbarie802aca42019-05-21 12:19:28 -0400165var proxyOperationTypes = []string{
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400166 "PROXY_NONE",
Stephane Barbarie802aca42019-05-21 12:19:28 -0400167 "PROXY_GET",
168 "PROXY_LIST",
169 "PROXY_ADD",
170 "PROXY_UPDATE",
171 "PROXY_REMOVE",
172 "PROXY_CREATE",
Stephane Barbariec92d1072019-06-07 16:21:49 -0400173 "PROXY_WATCH",
Stephane Barbarie802aca42019-05-21 12:19:28 -0400174}
175
176func (t ProxyOperation) String() string {
177 return proxyOperationTypes[t]
178}
179
npujar9a30c702019-11-14 17:06:39 +0530180// GetOperation -
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400181func (p *Proxy) GetOperation() ProxyOperation {
182 p.mutex.RLock()
183 defer p.mutex.RUnlock()
184 return p.operation
185}
186
npujar9a30c702019-11-14 17:06:39 +0530187// SetOperation -
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400188func (p *Proxy) SetOperation(operation ProxyOperation) {
189 p.mutex.Lock()
190 defer p.mutex.Unlock()
191 p.operation = operation
192}
193
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500194// parseForControlledPath verifies if a proxy path matches a pattern
195// for locations that need to be access controlled.
Stephane Barbariea188d942018-10-16 16:43:04 -0400196func (p *Proxy) parseForControlledPath(path string) (pathLock string, controlled bool) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500197 // TODO: Add other path prefixes that may need control
Stephane Barbarie1ab43272018-12-08 21:42:13 -0500198 if strings.HasPrefix(path, "/devices") ||
199 strings.HasPrefix(path, "/logical_devices") ||
200 strings.HasPrefix(path, "/adapters") {
201
Stephane Barbariea188d942018-10-16 16:43:04 -0400202 split := strings.SplitN(path, "/", -1)
203 switch len(split) {
204 case 2:
205 controlled = false
206 pathLock = ""
Stephane Barbariea188d942018-10-16 16:43:04 -0400207 case 3:
208 fallthrough
209 default:
210 pathLock = fmt.Sprintf("%s/%s", split[1], split[2])
211 controlled = true
212 }
213 }
214 return pathLock, controlled
215}
216
Stephane Barbarieaa467942019-02-06 14:09:44 -0500217// List will retrieve information from the data model at the specified path location
218// A list operation will force access to persistence storage
Thomas Lee Se5a44012019-11-07 20:32:24 +0530219func (p *Proxy) List(ctx context.Context, path string, depth int, deep bool, txid string) (interface{}, error) {
Stephane Barbarieaa467942019-02-06 14:09:44 -0500220 var effectivePath string
221 if path == "/" {
222 effectivePath = p.getFullPath()
223 } else {
224 effectivePath = p.getFullPath() + path
225 }
226
227 pathLock, controlled := p.parseForControlledPath(effectivePath)
228
npujar9a30c702019-11-14 17:06:39 +0530229 p.SetOperation(ProxyList)
230 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400231
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400232 log.Debugw("proxy-list", log.Fields{
233 "path": path,
234 "effective": effectivePath,
235 "pathLock": pathLock,
236 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400237 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400238 })
npujar9a30c702019-11-14 17:06:39 +0530239 return p.getRoot().List(ctx, path, "", depth, deep, txid)
Stephane Barbarieaa467942019-02-06 14:09:44 -0500240}
241
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500242// Get will retrieve information from the data model at the specified path location
Thomas Lee Se5a44012019-11-07 20:32:24 +0530243func (p *Proxy) Get(ctx context.Context, path string, depth int, deep bool, txid string) (interface{}, error) {
Stephane Barbariea188d942018-10-16 16:43:04 -0400244 var effectivePath string
245 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500246 effectivePath = p.getFullPath()
Stephane Barbariea188d942018-10-16 16:43:04 -0400247 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500248 effectivePath = p.getFullPath() + path
Stephane Barbariea188d942018-10-16 16:43:04 -0400249 }
250
251 pathLock, controlled := p.parseForControlledPath(effectivePath)
252
npujar9a30c702019-11-14 17:06:39 +0530253 p.SetOperation(ProxyGet)
254 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400255
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400256 log.Debugw("proxy-get", log.Fields{
257 "path": path,
258 "effective": effectivePath,
259 "pathLock": pathLock,
260 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400261 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400262 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400263
npujar9a30c702019-11-14 17:06:39 +0530264 return p.getRoot().Get(ctx, path, "", depth, deep, txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400265}
266
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500267// Update will modify information in the data model at the specified location with the provided data
Thomas Lee Se5a44012019-11-07 20:32:24 +0530268func (p *Proxy) Update(ctx context.Context, path string, data interface{}, strict bool, txid string) (interface{}, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400269 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400270 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530271 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400272 }
273 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400274 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400275 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500276 fullPath = p.getPath()
277 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400278 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500279 fullPath = p.getPath() + path
Stephane Barbarie1039ec42019-02-04 10:43:16 -0500280 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400281 }
Stephane Barbarie126101e2018-10-11 16:18:48 -0400282
Stephane Barbariea188d942018-10-16 16:43:04 -0400283 pathLock, controlled := p.parseForControlledPath(effectivePath)
284
npujar9a30c702019-11-14 17:06:39 +0530285 p.SetOperation(ProxyUpdate)
286 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400287
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400288 log.Debugw("proxy-update", log.Fields{
289 "path": path,
290 "effective": effectivePath,
291 "full": fullPath,
292 "pathLock": pathLock,
293 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400294 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400295 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400296
npujar9a30c702019-11-14 17:06:39 +0530297 if p.getRoot().KvStore != nil {
npujar467fe752020-01-16 20:17:45 +0530298 if _, err := p.getRoot().KvStore.Client.Reserve(ctx, pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530299 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
300 return nil, err
301 }
npujar9a30c702019-11-14 17:06:39 +0530302 defer func() {
npujar467fe752020-01-16 20:17:45 +0530303 err := p.getRoot().KvStore.Client.ReleaseReservation(ctx, pathLock+"_")
npujar9a30c702019-11-14 17:06:39 +0530304 if err != nil {
305 log.Errorw("Unable to release reservation for key", log.Fields{"error": err})
306 }
307 }()
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400308 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500309
npujar9a30c702019-11-14 17:06:39 +0530310 result := p.getRoot().Update(ctx, fullPath, data, strict, txid, nil)
Stephane Barbariec53a2752019-03-08 17:50:10 -0500311
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400312 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530313 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400314 }
315
Thomas Lee Se5a44012019-11-07 20:32:24 +0530316 return nil, nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400317}
318
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500319// AddWithID will insert new data at specified location.
320// This method also allows the user to specify the ID of the data entry to ensure
321// that access control is active while inserting the information.
Thomas Lee Se5a44012019-11-07 20:32:24 +0530322func (p *Proxy) AddWithID(ctx context.Context, path string, id string, data interface{}, txid string) (interface{}, error) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500323 if !strings.HasPrefix(path, "/") {
324 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530325 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500326 }
327 var fullPath string
328 var effectivePath string
329 if path == "/" {
330 fullPath = p.getPath()
331 effectivePath = p.getFullPath()
332 } else {
333 fullPath = p.getPath() + path
334 effectivePath = p.getFullPath() + path + "/" + id
335 }
336
337 pathLock, controlled := p.parseForControlledPath(effectivePath)
338
npujar9a30c702019-11-14 17:06:39 +0530339 p.SetOperation(ProxyAdd)
340 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400341
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400342 log.Debugw("proxy-add-with-id", log.Fields{
343 "path": path,
344 "effective": effectivePath,
345 "full": fullPath,
346 "pathLock": pathLock,
347 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400348 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400349 })
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500350
npujar9a30c702019-11-14 17:06:39 +0530351 if p.getRoot().KvStore != nil {
npujar467fe752020-01-16 20:17:45 +0530352 if _, err := p.getRoot().KvStore.Client.Reserve(ctx, pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530353 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
354 return nil, err
355 }
npujar9a30c702019-11-14 17:06:39 +0530356 defer func() {
npujar467fe752020-01-16 20:17:45 +0530357 err := p.getRoot().KvStore.Client.ReleaseReservation(ctx, pathLock+"_")
npujar9a30c702019-11-14 17:06:39 +0530358 if err != nil {
359 log.Errorw("Unable to release reservation for key", log.Fields{"error": err})
360 }
361 }()
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400362 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500363
npujar9a30c702019-11-14 17:06:39 +0530364 result := p.getRoot().Add(ctx, fullPath, data, txid, nil)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400365
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400366 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530367 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400368 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500369
Thomas Lee Se5a44012019-11-07 20:32:24 +0530370 return nil, nil
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500371}
372
373// Add will insert new data at specified location.
Thomas Lee Se5a44012019-11-07 20:32:24 +0530374func (p *Proxy) Add(ctx context.Context, path string, data interface{}, txid string) (interface{}, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400375 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400376 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530377 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400378 }
379 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400380 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400381 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500382 fullPath = p.getPath()
383 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400384 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500385 fullPath = p.getPath() + path
386 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400387 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400388
389 pathLock, controlled := p.parseForControlledPath(effectivePath)
390
npujar9a30c702019-11-14 17:06:39 +0530391 p.SetOperation(ProxyAdd)
392 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400393
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400394 log.Debugw("proxy-add", log.Fields{
395 "path": path,
396 "effective": effectivePath,
397 "full": fullPath,
398 "pathLock": pathLock,
399 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400400 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400401 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400402
npujar9a30c702019-11-14 17:06:39 +0530403 if p.getRoot().KvStore != nil {
npujar467fe752020-01-16 20:17:45 +0530404 if _, err := p.getRoot().KvStore.Client.Reserve(ctx, pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530405 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
406 return nil, err
407 }
npujar9a30c702019-11-14 17:06:39 +0530408 defer func() {
npujar467fe752020-01-16 20:17:45 +0530409 err := p.getRoot().KvStore.Client.ReleaseReservation(ctx, pathLock+"_")
npujar9a30c702019-11-14 17:06:39 +0530410 if err != nil {
411 log.Errorw("Unable to release reservation for key", log.Fields{"error": err})
412 }
413 }()
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400414 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500415
npujar9a30c702019-11-14 17:06:39 +0530416 result := p.getRoot().Add(ctx, fullPath, data, txid, nil)
Stephane Barbariea188d942018-10-16 16:43:04 -0400417
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400418 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530419 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400420 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500421
Thomas Lee Se5a44012019-11-07 20:32:24 +0530422 return nil, nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400423}
424
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500425// Remove will delete an entry at the specified location
Thomas Lee Se5a44012019-11-07 20:32:24 +0530426func (p *Proxy) Remove(ctx context.Context, path string, txid string) (interface{}, error) {
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400427 if !strings.HasPrefix(path, "/") {
Stephane Barbarie8c48b5c2018-10-02 09:45:17 -0400428 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530429 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400430 }
431 var fullPath string
Stephane Barbariea188d942018-10-16 16:43:04 -0400432 var effectivePath string
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400433 if path == "/" {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500434 fullPath = p.getPath()
435 effectivePath = p.getFullPath()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400436 } else {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500437 fullPath = p.getPath() + path
438 effectivePath = p.getFullPath() + path
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400439 }
Stephane Barbariea188d942018-10-16 16:43:04 -0400440
441 pathLock, controlled := p.parseForControlledPath(effectivePath)
442
npujar9a30c702019-11-14 17:06:39 +0530443 p.SetOperation(ProxyRemove)
444 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400445
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400446 log.Debugw("proxy-remove", log.Fields{
447 "path": path,
448 "effective": effectivePath,
449 "full": fullPath,
450 "pathLock": pathLock,
451 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400452 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400453 })
Stephane Barbariea188d942018-10-16 16:43:04 -0400454
npujar9a30c702019-11-14 17:06:39 +0530455 if p.getRoot().KvStore != nil {
npujar467fe752020-01-16 20:17:45 +0530456 if _, err := p.getRoot().KvStore.Client.Reserve(ctx, pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530457 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
458 return nil, err
459 }
npujar9a30c702019-11-14 17:06:39 +0530460 defer func() {
npujar467fe752020-01-16 20:17:45 +0530461 err := p.getRoot().KvStore.Client.ReleaseReservation(ctx, pathLock+"_")
npujar9a30c702019-11-14 17:06:39 +0530462 if err != nil {
463 log.Errorw("Unable to release reservation for key", log.Fields{"error": err})
464 }
465 }()
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400466 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500467
npujar9a30c702019-11-14 17:06:39 +0530468 result := p.getRoot().Remove(ctx, fullPath, txid, nil)
Stephane Barbariea188d942018-10-16 16:43:04 -0400469
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400470 if result != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530471 return result.GetData(), nil
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400472 }
Stephane Barbariec53a2752019-03-08 17:50:10 -0500473
Thomas Lee Se5a44012019-11-07 20:32:24 +0530474 return nil, nil
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400475}
476
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400477// CreateProxy to interact with specific path directly
Thomas Lee Se5a44012019-11-07 20:32:24 +0530478func (p *Proxy) CreateProxy(ctx context.Context, path string, exclusive bool) (*Proxy, error) {
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400479 if !strings.HasPrefix(path, "/") {
480 log.Errorf("invalid path: %s", path)
Thomas Lee Se5a44012019-11-07 20:32:24 +0530481 return nil, fmt.Errorf("invalid path: %s", path)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400482 }
483
484 var fullPath string
485 var effectivePath string
486 if path == "/" {
487 fullPath = p.getPath()
488 effectivePath = p.getFullPath()
489 } else {
490 fullPath = p.getPath() + path
491 effectivePath = p.getFullPath() + path
492 }
493
494 pathLock, controlled := p.parseForControlledPath(effectivePath)
495
npujar9a30c702019-11-14 17:06:39 +0530496 p.SetOperation(ProxyCreate)
497 defer p.SetOperation(ProxyNone)
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400498
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400499 log.Debugw("proxy-create", log.Fields{
500 "path": path,
501 "effective": effectivePath,
502 "full": fullPath,
503 "pathLock": pathLock,
504 "controlled": controlled,
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400505 "operation": p.GetOperation(),
Stephane Barbarie7512fc82019-05-07 12:25:46 -0400506 })
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400507
npujar9a30c702019-11-14 17:06:39 +0530508 if p.getRoot().KvStore != nil {
npujar467fe752020-01-16 20:17:45 +0530509 if _, err := p.getRoot().KvStore.Client.Reserve(ctx, pathLock+"_", uuid.New().String(), ReservationTTL); err != nil {
Thomas Lee Se5a44012019-11-07 20:32:24 +0530510 log.Errorw("unable-to-acquire-key-from-kvstore", log.Fields{"error": err})
511 return nil, err
512 }
npujar9a30c702019-11-14 17:06:39 +0530513 defer func() {
npujar467fe752020-01-16 20:17:45 +0530514 err := p.getRoot().KvStore.Client.ReleaseReservation(ctx, pathLock+"_")
npujar9a30c702019-11-14 17:06:39 +0530515 if err != nil {
516 log.Errorw("Unable to release reservation for key", log.Fields{"error": err})
517 }
518 }()
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400519 }
npujar9a30c702019-11-14 17:06:39 +0530520 return p.getRoot().CreateProxy(ctx, fullPath, exclusive)
Stephane Barbarie40fd3b22019-04-23 21:50:47 -0400521}
522
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500523// OpenTransaction creates a new transaction branch to isolate operations made to the data model
Stephane Barbarie88fbe7f2018-09-25 12:25:23 -0400524func (p *Proxy) OpenTransaction() *Transaction {
npujar9a30c702019-11-14 17:06:39 +0530525 txid := p.getRoot().MakeTxBranch()
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400526 return NewTransaction(p, txid)
527}
528
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500529// commitTransaction will apply and merge modifications made in the transaction branch to the data model
npujar467fe752020-01-16 20:17:45 +0530530func (p *Proxy) commitTransaction(ctx context.Context, txid string) {
531 p.getRoot().FoldTxBranch(ctx, txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400532}
533
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500534// cancelTransaction will terminate a transaction branch along will all changes within it
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400535func (p *Proxy) cancelTransaction(txid string) {
npujar9a30c702019-11-14 17:06:39 +0530536 p.getRoot().DeleteTxBranch(txid)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400537}
538
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500539// CallbackFunction is a type used to define callback functions
npujar467fe752020-01-16 20:17:45 +0530540type CallbackFunction func(ctx context.Context, args ...interface{}) interface{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500541
542// CallbackTuple holds the function and arguments details of a callback
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400543type CallbackTuple struct {
544 callback CallbackFunction
545 args []interface{}
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400546}
547
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500548// Execute will process the a callback with its provided arguments
npujar467fe752020-01-16 20:17:45 +0530549func (tuple *CallbackTuple) Execute(ctx context.Context, contextArgs []interface{}) interface{} {
Stephane Barbarie126101e2018-10-11 16:18:48 -0400550 args := []interface{}{}
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500551
npujar9a30c702019-11-14 17:06:39 +0530552 args = append(args, tuple.args...)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500553
npujar9a30c702019-11-14 17:06:39 +0530554 args = append(args, contextArgs...)
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500555
npujar467fe752020-01-16 20:17:45 +0530556 return tuple.callback(ctx, args...)
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400557}
558
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500559// RegisterCallback associates a callback to the proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400560func (p *Proxy) RegisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500561 if p.getCallbacks(callbackType) == nil {
562 p.setCallbacks(callbackType, make(map[string]*CallbackTuple))
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400563 }
564 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
565 log.Debugf("value of function: %s", funcName)
566 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
567
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500568 p.setCallback(callbackType, funcHash, &CallbackTuple{callback, args})
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400569}
570
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500571// UnregisterCallback removes references to a callback within a proxy
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400572func (p *Proxy) UnregisterCallback(callbackType CallbackType, callback CallbackFunction, args ...interface{}) {
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500573 if p.getCallbacks(callbackType) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400574 log.Errorf("no such callback type - %s", callbackType.String())
575 return
576 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500577
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400578 funcName := runtime.FuncForPC(reflect.ValueOf(callback).Pointer()).Name()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400579 funcHash := fmt.Sprintf("%x", md5.Sum([]byte(funcName)))[:12]
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500580
581 log.Debugf("value of function: %s", funcName)
582
583 if p.getCallback(callbackType, funcHash) == nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400584 log.Errorf("function with hash value: '%s' not registered with callback type: '%s'", funcHash, callbackType)
585 return
586 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500587
588 p.DeleteCallback(callbackType, funcHash)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400589}
590
npujar467fe752020-01-16 20:17:45 +0530591func (p *Proxy) invoke(ctx context.Context, callback *CallbackTuple, context []interface{}) (result interface{}, err error) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400592 defer func() {
593 if r := recover(); r != nil {
594 errStr := fmt.Sprintf("callback error occurred: %+v", r)
595 err = errors.New(errStr)
596 log.Error(errStr)
597 }
598 }()
599
npujar467fe752020-01-16 20:17:45 +0530600 result = callback.Execute(ctx, context)
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400601
602 return result, err
603}
604
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500605// InvokeCallbacks executes all callbacks associated to a specific type
npujar467fe752020-01-16 20:17:45 +0530606func (p *Proxy) InvokeCallbacks(ctx context.Context, args ...interface{}) (result interface{}) {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400607 callbackType := args[0].(CallbackType)
Stephane Barbarie126101e2018-10-11 16:18:48 -0400608 proceedOnError := args[1].(bool)
609 context := args[2:]
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400610
611 var err error
612
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500613 if callbacks := p.getCallbacks(callbackType); callbacks != nil {
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400614 p.mutex.Lock()
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500615 for _, callback := range callbacks {
npujar467fe752020-01-16 20:17:45 +0530616 if result, err = p.invoke(ctx, callback, context); err != nil {
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400617 if !proceedOnError {
618 log.Info("An error occurred. Stopping callback invocation")
619 break
620 }
621 log.Info("An error occurred. Invoking next callback")
622 }
623 }
Stephane Barbarieef6650d2019-07-18 12:15:09 -0400624 p.mutex.Unlock()
Stephane Barbarie694e2b92018-09-07 12:17:36 -0400625 }
Stephane Barbariedc5022d2018-11-19 15:21:44 -0500626
Stephane Barbariea188d942018-10-16 16:43:04 -0400627 return result
Stephane Barbarie4a2564d2018-07-26 11:02:58 -0400628}