+ private synchronized void initOperationalTopology() {
+ Preconditions.checkNotNull(this.chain, "A valid transaction chain must be provided.");
+ final WriteTransaction trans = this.chain.newWriteOnlyTransaction();
+ trans.put(LogicalDatastoreType.OPERATIONAL, this.topology,
+ new TopologyBuilder().setKey(this.topologyKey).setServerProvided(Boolean.TRUE).setTopologyTypes(this.topologyTypes)
+ .setLink(Collections.<Link>emptyList()).setNode(Collections.<Node>emptyList()).build(), true);
+ Futures.addCallback(trans.submit(), new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(final Void result) {
+ LOG.trace("Transaction {} committed successfully", trans.getIdentifier());
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ LOG.error("Failed to initialize topology {} (transaction {}) by listener {}", AbstractTopologyBuilder.this.topology,
+ trans.getIdentifier(), AbstractTopologyBuilder.this, t);
+ }
+ });
+ }
+
+ /**
+ * Destroy the current operational topology data. Note a valid transaction must be provided
+ * @throws TransactionCommitFailedException
+ */
+ private synchronized void destroyOperationalTopology() {
+ Preconditions.checkNotNull(this.chain, "A valid transaction chain must be provided.");
+ final WriteTransaction trans = this.chain.newWriteOnlyTransaction();
+ trans.delete(LogicalDatastoreType.OPERATIONAL, getInstanceIdentifier());
+ try {
+ trans.submit().checkedGet();
+ } catch (TransactionCommitFailedException e) {
+ LOG.error("Unable to reset operational topology {} (transaction {})", this.topology, trans.getIdentifier(), e);
+ }
+ }
+
+ /**
+ * Reset a transaction chain by closing the current chain and starting a new one
+ */
+ private synchronized void initTransactionChain() {
+ LOG.debug("Initializing transaction chain for topology {}", this);
+ Preconditions.checkState(this.chain == null, "Transaction chain has to be closed before being initialized");
+ this.chain = this.dataProvider.createTransactionChain(this);
+ }
+
+ /**
+ * Destroy the current transaction chain
+ */
+ private synchronized void destroyTransactionChain() {
+ if (this.chain != null) {
+ LOG.debug("Destroy transaction chain for topology {}", this);
+ // we cannot close the transaction chain, as it will close the AbstractDOMForwardedTransactionFactory
+ // and the transaction factory cannot be reopen even if we recreate the transaction chain
+ // so we abandon the chain directly
+ // FIXME we want to close the transaction chain gracefully once the PingPongTransactionChain get improved
+ // and the above problem get resolved.
+// try {
+// this.chain.close();
+// } catch (Exception e) {
+// // the close() may not succeed when the transaction chain is locked
+// LOG.error("Unable to close transaction chain {} for topology builder {}", this.chain, getInstanceIdentifier());
+// }
+ this.chain = null;
+ }
+ }
+
+ /**
+ * Reset the data change listener to its initial status
+ * By resetting the listener we will be able to recover all the data lost before
+ */
+ @VisibleForTesting
+ protected synchronized void resetListener() {
+ Preconditions.checkNotNull(this.listenerRegistration, "Listener on topology %s hasn't been initialized.", this);
+ LOG.debug("Resetting data change listener for topology builder {}", getInstanceIdentifier());
+ // unregister current listener to prevent incoming data tree change first
+ unregisterDataChangeListener();
+ // create new transaction chain to reset the chain status
+ resetTransactionChain();
+ // reset the operational topology data so that we can have clean status
+ destroyOperationalTopology();
+ initOperationalTopology();
+ // re-register the data change listener to reset the operational topology
+ // we are expecting to receive all the pre-exist route change on the next onDataTreeChanged() call
+ registerDataChangeListener();
+ }
+
+ /**
+ * Reset the transaction chain only so that the PingPong transaction chain will become usable again.
+ * However, there will be data loss if we do not apply the previous failed transaction again
+ */
+ @VisibleForTesting
+ protected synchronized void resetTransactionChain() {
+ LOG.debug("Resetting transaction chain for topology builder {}", getInstanceIdentifier());
+ destroyTransactionChain();
+ initTransactionChain();
+ }
+
+ /**
+ * There are a few reasons we want to schedule a listener restart in a delayed manner:
+ * 1. we should avoid restarting the listener as when the topology is big, there might be huge overhead
+ * rebuilding the whole linkstate topology again and again
+ * 2. the #onTransactionChainFailed() normally get invoked after a delay. During that time gap, more
+ * data changes might still be pushed to #onDataTreeChanged(). And because #onTransactionChainFailed()
+ * is not invoked yet, listener restart/transaction chain restart is not done. Thus the new changes
+ * will still cause error and another #onTransactionChainFailed() might be invoked later. The listener
+ * will be restarted again in that case, which is unexpected. Restarting of transaction chain only introduce
+ * little overhead and it's okay to be restarted within a small time window
+ *
+ * Note: when the listener is restarted, we can disregard all the incoming data changes before the restart is
+ * done, as after the listener unregister/reregister, the first #onDataTreeChanged() call will contain the a
+ * complete set of existing changes
+ *
+ * @return if the listener get restarted, return true; otherwise false
+ */
+ @VisibleForTesting
+ protected synchronized boolean restartTransactionChainOnDemand() {
+ if (this.listenerScheduledRestartTime > 0) {
+ // when the #this.listenerScheduledRestartTime timer timed out we can reset the listener, otherwise we should only reset the transaction chain
+ if (System.currentTimeMillis() > this.listenerScheduledRestartTime) {
+ // reset the the restart timer
+ this.listenerScheduledRestartTime = 0;
+ this.listenerScheduledRestartEnforceCounter = 0;
+ resetListener();
+ return true;
+ } else {
+ resetTransactionChain();
+ }
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ protected synchronized void scheduleListenerRestart() {
+ if (0 == this.listenerScheduledRestartTime) {
+ this.listenerScheduledRestartTime = System.currentTimeMillis() + this.listenerResetLimitInMillsec;
+ } else if (System.currentTimeMillis() > this.listenerScheduledRestartTime
+ && ++this.listenerScheduledRestartEnforceCounter < this.listenerResetEnforceCounter) {
+ // if the transaction failure happens again, we will delay the listener restart up to #LISTENER_RESET_LIMIT_IN_MILLSEC times
+ this.listenerScheduledRestartTime += this.listenerResetLimitInMillsec;
+ }
+ LOG.debug("A listener restart was scheduled at {} (current system time is {})", this.listenerScheduledRestartTime, System.currentTimeMillis());
+ }
+