blob: 57d815070bf8c619ba6b44547b6c6f20323a7d52 [file] [log] [blame]
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -05001#
2# Copyright 2017 the original author or authors.
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#
16import structlog
17from enum import Enum
18
19from voltha.core.config.config_txn import ConfigTransaction
20
21log = structlog.get_logger()
22
23
24class OperationContext(object):
25 def __init__(self, path=None, data=None, field_name=None, child_key=None):
26 self.path = path
27 self._data = data
28 self.field_name = field_name
29 self.child_key = child_key
30 @property
31 def data(self):
32 return self._data
33 def update(self, data):
34 self._data = data
35 return self
36 def __repr__(self):
37 return 'OperationContext({})'.format(self.__dict__)
38
39
40class CallbackType(Enum):
41
42 # GET hooks are called after the data is retrieved and can be used to
43 # augment the data (they should only augment fields marked as REAL_TIME
44 GET = 1
45
46 # PRE_UPDATE hooks are called before the change is made and are supposed
47 # to be used to reject the data by raising an exception. If they don't,
48 # the change will be applied.
49 PRE_UPDATE = 2
50
51 # POST_UPDATE hooks are called after the update has occurred and can
52 # be used to deal with the change. For instance, an adapter can use the
53 # callback to trigger the south-bound configuration
54 POST_UPDATE = 3
55
56 # These behave similarly to the update callbacks as described above.
57 PRE_ADD = 4
58 POST_ADD = 5
59 PRE_REMOVE = 6
60 POST_REMOVE = 7
61
62 # Bulk list change due to transaction commit that changed items in
63 # non-keyed container fields
64 POST_LISTCHANGE = 8
65
66
67class ConfigProxy(object):
68 """
69 Allows an entity to look at a sub-tree and see it as it was the whole tree
70 """
71 __slots__ = (
72 '_root',
73 '_node',
74 '_path',
75 '_exclusive',
76 '_callbacks'
77 )
78
79 def __init__(self, root, node, path, exclusive):
80 self._root = root
81 self._node = node
82 self._exclusive = exclusive
83 self._path = path # full path to proxied node
84 self._callbacks = {} # call back type -> list of callbacks
85
86 @property
87 def exclusive(self):
88 return self._exclusive
89
90 # ~~~~~~~~~~~~~~~~~~~~~~~~~~ CRUD handlers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
91
92 def get(self, path='/', depth=None, deep=None, txid=None):
93 return self._node.get(path, depth=depth, deep=deep, txid=txid)
94
95 def update(self, path, data, strict=False, txid=None):
96 assert path.startswith('/')
97 full_path = self._path if path == '/' else self._path + path
98 return self._root.update(full_path, data, strict, txid=txid)
99
100 def add(self, path, data, txid=None):
101 assert path.startswith('/')
102 full_path = self._path if path == '/' else self._path + path
103 return self._root.add(full_path, data, txid=txid)
104
105 def remove(self, path, txid=None):
106 assert path.startswith('/')
107 full_path = self._path if path == '/' else self._path + path
108 return self._root.remove(full_path, txid=txid)
109
110 # ~~~~~~~~~~~~~~~~~~~~~~~~~~ Transaction support ~~~~~~~~~~~~~~~~~~~~~~~~~~
111
112 def open_transaction(self):
113 """Open a new transaction"""
114 txid = self._root.mk_txbranch()
115 return ConfigTransaction(self, txid)
116
117 def commit_transaction(self, txid):
118 """
119 If having an open transaction, commit it now. Will raise exception
120 if conflict is detected. Either way, transaction will be deleted.
121 """
122 self._root.fold_txbranch(txid)
123
124 def cancel_transaction(self, txid):
125 """
126 Cancel current transaction if we are in a transaction. Always succeeds.
127 """
128 self._root.del_txbranch(txid)
129
130 # ~~~~~~~~~~~~~~~~~~~~~~ Callbacks registrations ~~~~~~~~~~~~~~~~~~~~~~~~~~
131
132 def register_callback(self, callback_type, callback, *args, **kw):
133 lst = self._callbacks.setdefault(callback_type, [])
134 lst.append((callback, args, kw))
135
136 def unregister_callback(self, callback_type, callback, *args, **kw):
137 lst = self._callbacks.setdefault(callback_type, [])
138 if (callback, args, kw) in lst:
139 lst.remove((callback, args, kw))
140
141 # ~~~~~~~~~~~~~~~~~~~~~ Callback dispatch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
142
143 def invoke_callbacks(self, callback_type, context, proceed_on_errors=False):
144 lst = self._callbacks.get(callback_type, [])
145 for callback, args, kw in lst:
146 try:
147 context = callback(context, *args, **kw)
148 except Exception, e:
149 if proceed_on_errors:
150 log.exception(
151 'call-back-error', callback_type=callback_type,
152 context=context, e=e)
153 else:
154 raise
155 return context