Merge "RPC input is wrapped into RPC name element"
[controller.git] / opendaylight / netconf / config-persister-impl / src / main / java / org / opendaylight / controller / netconf / persist / impl / ConfigPersisterNotificationHandler.java
index a569f90538104048026249370a6650119007f32f..b20577afd23fcdde6bb8f3ab50b3f7f73c0c0a6f 100644 (file)
@@ -13,6 +13,7 @@ import com.google.common.base.Preconditions;
 import com.google.common.collect.Sets;
 import io.netty.channel.EventLoopGroup;
 import io.netty.channel.nio.NioEventLoopGroup;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
 import org.opendaylight.controller.config.persist.api.Persister;
 import org.opendaylight.controller.netconf.api.NetconfMessage;
@@ -21,6 +22,7 @@ import org.opendaylight.controller.netconf.api.jmx.DefaultCommitOperationMXBean;
 import org.opendaylight.controller.netconf.api.jmx.NetconfJMXNotification;
 import org.opendaylight.controller.netconf.client.NetconfClient;
 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
+import org.opendaylight.controller.netconf.util.xml.XMLNetconfUtil;
 import org.opendaylight.controller.netconf.util.xml.XmlElement;
 import org.opendaylight.controller.netconf.util.xml.XmlNetconfConstants;
 import org.opendaylight.controller.netconf.util.xml.XmlUtil;
@@ -37,6 +39,8 @@ import javax.management.Notification;
 import javax.management.NotificationListener;
 import javax.management.ObjectName;
 import javax.net.ssl.SSLContext;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
@@ -66,7 +70,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
 
     private final Persister persister;
     private final MBeanServerConnection mbeanServer;
-    private Long currentSessionId;
+
 
     private final ObjectName on = DefaultCommitOperationMXBean.objectName;
 
@@ -96,26 +100,40 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
 
         if (maybeConfig.isPresent()) {
             logger.debug("Last config found {}", persister);
+            ConflictingVersionException lastException = null;
+            pushLastConfigWithRetries(maybeConfig, lastException);
+
+        } else {
+            // this ensures that netconf is initialized, this is first
+            // connection
+            // this means we can register as listener for commit
+            registerToNetconf(Collections.<String>emptySet());
 
+            logger.info("No last config provided by backend storage {}", persister);
+        }
+        registerAsJMXListener();
+    }
+
+    private void pushLastConfigWithRetries(Optional<ConfigSnapshotHolder> maybeConfig, ConflictingVersionException lastException) throws InterruptedException {
+        int maxAttempts = 30;
+        for(int i = 0 ; i < maxAttempts; i++) {
             registerToNetconf(maybeConfig.get().getCapabilities());
 
             final String configSnapshot = maybeConfig.get().getConfigSnapshot();
             logger.trace("Pushing following xml to netconf {}", configSnapshot);
             try {
                 pushLastConfig(XmlUtil.readXmlToElement(configSnapshot));
+                return;
+            } catch(ConflictingVersionException e) {
+                closeClientAndDispatcher(netconfClient, netconfClientDispatcher);
+                lastException = e;
+                Thread.sleep(1000);
             } catch (SAXException | IOException e) {
                 throw new IllegalStateException("Unable to load last config", e);
             }
-
-        } else {
-            // this ensures that netconf is initialized, this is first
-            // connection
-            // this means we can register as listener for commit
-            registerToNetconf(Collections.<String>emptySet());
-
-            logger.info("No last config provided by backend storage {}", persister);
         }
-        registerAsJMXListener();
+        throw new IllegalStateException("Failed to push configuration, maximum attempt count has been reached: "
+                + maxAttempts, lastException);
     }
 
     private synchronized long registerToNetconf(Set<String> expectedCaps) throws InterruptedException {
@@ -130,9 +148,9 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
 
         int attempt = 0;
 
-        while (true) {
+        long deadline = pollingStart + timeout;
+        while (System.currentTimeMillis() < deadline) {
             attempt++;
-
             netconfClientDispatcher = new NetconfClientDispatcher(Optional.<SSLContext>absent(), nettyThreadgroup, nettyThreadgroup);
             try {
                 netconfClient = new NetconfClient(this.toString(), address, delay, netconfClientDispatcher);
@@ -146,14 +164,12 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
 
             if (isSubset(currentCapabilities, expectedCaps)) {
                 logger.debug("Hello from netconf stable with {} capabilities", currentCapabilities);
-                currentSessionId = netconfClient.getSessionId();
+                long currentSessionId = netconfClient.getSessionId();
                 logger.info("Session id received from netconf server: {}", currentSessionId);
                 return currentSessionId;
             }
 
-            if (System.currentTimeMillis() > pollingStart + timeout) {
-                break;
-            }
+
 
             logger.debug("Polling hello from netconf, attempt {}, capabilities {}", attempt, currentCapabilities);
 
@@ -197,6 +213,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
     }
 
     private void registerAsJMXListener() {
+        logger.trace("Called registerAsJMXListener");
         try {
             mbeanServer.addNotificationListener(on, this, null, null);
         } catch (InstanceNotFoundException | IOException e) {
@@ -212,7 +229,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
         // Socket should not be closed at this point
         // Activator unregisters this as JMX listener before close is called
 
-        logger.debug("Received notification {}", notification);
+        logger.info("Received notification {}", notification);
         if (notification instanceof CommitJMXNotification) {
             try {
                 handleAfterCommitNotification((CommitJMXNotification) notification);
@@ -229,7 +246,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
         try {
             persister.persistConfig(new CapabilityStrippingConfigSnapshotHolder(notification.getConfigSnapshot(),
                     notification.getCapabilities(), ignoredMissingCapabilityRegex));
-            logger.debug("Configuration persisted successfully");
+            logger.info("Configuration persisted successfully");
         } catch (IOException e) {
             throw new RuntimeException("Unable to persist configuration snapshot", e);
         }
@@ -245,7 +262,7 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
         return maybeConfigElement;
     }
 
-    private synchronized void pushLastConfig(Element xmlToBePersisted) {
+    private synchronized void pushLastConfig(Element xmlToBePersisted) throws ConflictingVersionException {
         logger.info("Pushing last configuration to netconf");
         StringBuilder response = new StringBuilder("editConfig response = {");
 
@@ -276,19 +293,26 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
         logger.trace("Detailed message {}", response);
     }
 
-    private void checkIsOk(XmlElement element, NetconfMessage responseMessage) {
+    static void checkIsOk(XmlElement element, NetconfMessage responseMessage) throws ConflictingVersionException {
         if (element.getName().equals(XmlNetconfConstants.OK)) {
             return;
-        } else {
-            if (element.getName().equals(XmlNetconfConstants.RPC_ERROR)) {
-                logger.warn("Can not load last configuration, operation failed");
-                throw new IllegalStateException("Can not load last configuration, operation failed: "
-                        + XmlUtil.toString(responseMessage.getDocument()));
+        }
+
+        if (element.getName().equals(XmlNetconfConstants.RPC_ERROR)) {
+            logger.warn("Can not load last configuration, operation failed");
+            // is it ConflictingVersionException ?
+            XPathExpression xPathExpression = XMLNetconfUtil.compileXPath("/netconf:rpc-reply/netconf:rpc-error/netconf:error-info/netconf:error");
+            String error = (String) XmlUtil.evaluateXPath(xPathExpression, element.getDomElement(), XPathConstants.STRING);
+            if (error!=null && error.contains(ConflictingVersionException.class.getCanonicalName())) {
+                throw new ConflictingVersionException(error);
             }
-            logger.warn("Can not load last configuration. Operation failed.");
-            throw new IllegalStateException("Can not load last configuration. Operation failed: "
+            throw new IllegalStateException("Can not load last configuration, operation failed: "
                     + XmlUtil.toString(responseMessage.getDocument()));
         }
+
+        logger.warn("Can not load last configuration. Operation failed.");
+        throw new IllegalStateException("Can not load last configuration. Operation failed: "
+                + XmlUtil.toString(responseMessage.getDocument()));
     }
 
     private static NetconfMessage createEditConfigMessage(Element dataElement, String editConfigResourcename) {