VOL-1451 Initial checkin of openonu build
Produced docker container capable of building and running
openonu/brcm_openonci_onu. Copied over current onu code
and resolved all imports by copying into the local source tree.
Change-Id: Ib9785d37afc65b7d32ecf74aee2456352626e2b6
diff --git a/python/core/config/config_root.py b/python/core/config/config_root.py
new file mode 100644
index 0000000..4b1006d
--- /dev/null
+++ b/python/core/config/config_root.py
@@ -0,0 +1,229 @@
+#
+# Copyright 2017 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from uuid import uuid4
+
+import structlog
+from simplejson import dumps, loads
+
+from voltha.core.config.config_node import ConfigNode
+from voltha.core.config.config_rev import ConfigRevision
+from voltha.core.config.config_rev_persisted import PersistedConfigRevision
+from voltha.core.config.merge_3way import MergeConflictException
+
+log = structlog.get_logger()
+
+
+class ConfigRoot(ConfigNode):
+
+ __slots__ = (
+ '_dirty_nodes', # holds set of modified nodes per transaction branch
+ '_kv_store',
+ '_loading',
+ '_rev_cls',
+ '_deferred_callback_queue',
+ '_notification_deferred_callback_queue'
+ )
+
+ def __init__(self, initial_data, kv_store=None, rev_cls=ConfigRevision):
+ self._kv_store = kv_store
+ self._dirty_nodes = {}
+ self._loading = False
+ if kv_store is not None and \
+ not issubclass(rev_cls, PersistedConfigRevision):
+ rev_cls = PersistedConfigRevision
+ self._rev_cls = rev_cls
+ self._deferred_callback_queue = []
+ self._notification_deferred_callback_queue = []
+ super(ConfigRoot, self).__init__(self, initial_data, False)
+
+ @property
+ def kv_store(self):
+ if self._loading:
+ # provide fake store for storing things
+ # TODO this shall be a fake_dict providing noop for all relevant
+ # operations
+ return dict()
+ else:
+ return self._kv_store
+
+ def mkrev(self, *args, **kw):
+ return self._rev_cls(*args, **kw)
+
+ def mk_txbranch(self):
+ txid = uuid4().hex[:12]
+ self._dirty_nodes[txid] = {self}
+ self._mk_txbranch(txid)
+ return txid
+
+ def del_txbranch(self, txid):
+ for dirty_node in self._dirty_nodes[txid]:
+ dirty_node._del_txbranch(txid)
+ del self._dirty_nodes[txid]
+
+ def fold_txbranch(self, txid):
+ try:
+ self._merge_txbranch(txid, dry_run=1)
+ except MergeConflictException:
+ self.del_txbranch(txid)
+ raise
+
+ try:
+ self._merge_txbranch(txid)
+ finally:
+ self.execute_deferred_callbacks()
+
+ # ~~~~~~ Overridden, root-level CRUD methods to handle transactions ~~~~~~~
+
+ def update(self, path, data, strict=None, txid=None, mk_branch=None):
+ assert mk_branch is None
+ self.check_callback_queue()
+ try:
+ if txid is not None:
+ dirtied = self._dirty_nodes[txid]
+
+ def track_dirty(node):
+ dirtied.add(node)
+ return node._mk_txbranch(txid)
+
+ res = super(ConfigRoot, self).update(path, data, strict,
+ txid, track_dirty)
+ else:
+ res = super(ConfigRoot, self).update(path, data, strict)
+ finally:
+ self.execute_deferred_callbacks()
+ return res
+
+ def add(self, path, data, txid=None, mk_branch=None):
+ assert mk_branch is None
+ self.check_callback_queue()
+ try:
+ if txid is not None:
+ dirtied = self._dirty_nodes[txid]
+
+ def track_dirty(node):
+ dirtied.add(node)
+ return node._mk_txbranch(txid)
+
+ res = super(ConfigRoot, self).add(path, data, txid, track_dirty)
+ else:
+ res = super(ConfigRoot, self).add(path, data)
+ finally:
+ self.execute_deferred_callbacks()
+ return res
+
+ def remove(self, path, txid=None, mk_branch=None):
+ assert mk_branch is None
+ self.check_callback_queue()
+ try:
+ if txid is not None:
+ dirtied = self._dirty_nodes[txid]
+
+ def track_dirty(node):
+ dirtied.add(node)
+ return node._mk_txbranch(txid)
+
+ res = super(ConfigRoot, self).remove(path, txid, track_dirty)
+ else:
+ res = super(ConfigRoot, self).remove(path)
+ finally:
+ self.execute_deferred_callbacks()
+ return res
+
+ def check_callback_queue(self):
+ assert len(self._deferred_callback_queue) == 0
+
+ def enqueue_callback(self, func, *args, **kw):
+ self._deferred_callback_queue.append((func, args, kw))
+
+ def enqueue_notification_callback(self, func, *args, **kw):
+ """
+ A separate queue is required for notification. Previously, when the
+ notifications were added to the self._deferred_callback_queue there
+ was a deadlock condition where two callbacks were added (one
+ related to the model change and one for the notification related to
+ that model change). Since the model change requires the
+ self._deferred_callback_queue to be empty then there was a deadlock
+ in that scenario. The simple approach to avoid this problem is to
+ have separate queues for model and notification.
+ TODO: Investigate whether there is a need for the
+ self._deferred_callback_queue to handle multiple model events at the same time
+ :param func: callback function
+ :param args: args
+ :param kw: key-value args
+ :return: None
+ """
+ self._notification_deferred_callback_queue.append((func, args, kw))
+
+ def execute_deferred_callbacks(self):
+ # First process the model-triggered related callbacks
+ while self._deferred_callback_queue:
+ func, args, kw = self._deferred_callback_queue.pop(0)
+ func(*args, **kw)
+
+ # Execute the notification callbacks
+ while self._notification_deferred_callback_queue:
+ func, args, kw = self._notification_deferred_callback_queue.pop(0)
+ func(*args, **kw)
+
+
+ # ~~~~~~~~~~~~~~~~ Persistence related ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ @classmethod
+ def load(cls, root_msg_cls, kv_store):
+ # need to use fake kv store during initial load for not to override
+ # our real k vstore
+ fake_kv_store = dict() # shall use more efficient mock dict
+ root = cls(root_msg_cls(), kv_store=fake_kv_store,
+ rev_cls=PersistedConfigRevision)
+ # we can install the real store now
+ root._kv_store = kv_store
+ root.load_from_persistence(root_msg_cls)
+ return root
+
+ def _make_latest(self, branch, *args, **kw):
+ super(ConfigRoot, self)._make_latest(branch, *args, **kw)
+ # only persist the committed branch
+ if self._kv_store is not None and branch._txid is None:
+ root_data = dict(
+ latest=branch._latest._hash,
+ tags=dict((k, v._hash) for k, v in self._tags.iteritems())
+ )
+ blob = dumps(root_data)
+ self._kv_store['root'] = blob
+
+ def persist_tags(self):
+ if self._kv_store is not None:
+ root_data = loads(self.kv_store['root'])
+ root_data = dict(
+ latest=root_data['latest'],
+ tags=dict((k, v._hash) for k, v in self._tags.iteritems())
+ )
+ blob = dumps(root_data)
+ self._kv_store['root'] = blob
+
+ def load_from_persistence(self, root_msg_cls):
+ self._loading = True
+ blob = self._kv_store['root']
+ root_data = loads(blob)
+
+ for tag, hash in root_data['tags'].iteritems():
+ self.load_latest(hash)
+ self._tags[tag] = self.latest
+
+ self.load_latest(root_data['latest'])
+
+ self._loading = False
+