BUG-2150 Report errors for semantic issues with config snapshots
[controller.git] / opendaylight / netconf / config-persister-impl / src / main / java / org / opendaylight / controller / netconf / persist / impl / ConfigPusherImpl.java
index 5f311b5232ed676263925ce6af94b779c662df2e..c41a2f4d16b7e325e228704d58f6e63f798a1700 100644 (file)
@@ -10,6 +10,9 @@ package org.opendaylight.controller.netconf.persist.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.common.base.Function;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Collections2;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collection;
@@ -23,18 +26,17 @@ import java.util.TreeMap;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-
+import javax.annotation.Nonnull;
 import javax.annotation.concurrent.Immutable;
 import javax.management.MBeanServerConnection;
-
 import org.opendaylight.controller.config.api.ConflictingVersionException;
 import org.opendaylight.controller.config.persist.api.ConfigPusher;
 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
 import org.opendaylight.controller.config.persist.api.Persister;
+import org.opendaylight.controller.netconf.api.Capability;
 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
 import org.opendaylight.controller.netconf.api.NetconfMessage;
 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
-import org.opendaylight.controller.netconf.mapping.api.Capability;
 import org.opendaylight.controller.netconf.mapping.api.HandlingPriority;
 import org.opendaylight.controller.netconf.mapping.api.NetconfOperation;
 import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution;
@@ -49,13 +51,9 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.xml.sax.SAXException;
 
-import com.google.common.base.Function;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.Collections2;
-
 @Immutable
 public class ConfigPusherImpl implements ConfigPusher {
-    private static final Logger logger = LoggerFactory.getLogger(ConfigPusherImpl.class);
+    private static final Logger LOG = LoggerFactory.getLogger(ConfigPusherImpl.class);
 
     private final long maxWaitForCapabilitiesMillis;
     private final long conflictingVersionTimeoutMillis;
@@ -80,40 +78,39 @@ public class ConfigPusherImpl implements ConfigPusher {
                 synchronized (autoCloseables) {
                     autoCloseables.add(jmxNotificationHandler);
                 }
-                /*
-                 * We have completed initial configuration. At this point
-                 * it is good idea to perform garbage collection to prune
-                 * any garbage we have accumulated during startup.
-                 */
-                logger.debug("Running post-initialization garbage collection...");
-                System.gc();
-                logger.debug("Post-initialization garbage collection completed.");
-                logger.debug("ConfigPusher has pushed configs {}, gc completed", configs);
-            }
-            catch (NetconfDocumentedException e) {
-                logger.error("Error pushing configs {}",configs);
+
+                LOG.debug("ConfigPusher has pushed configs {}", configs);
+            } catch (NetconfDocumentedException e) {
+                LOG.error("Error pushing configs {}",configs);
                 throw new IllegalStateException(e);
             }
         }
     }
 
     public void pushConfigs(List<? extends ConfigSnapshotHolder> configs) throws InterruptedException {
-        logger.debug("Requested to push configs {}", configs);
+        LOG.debug("Requested to push configs {}", configs);
         this.queue.put(configs);
     }
 
     private LinkedHashMap<? extends ConfigSnapshotHolder, EditAndCommitResponse> internalPushConfigs(List<? extends ConfigSnapshotHolder> configs) throws NetconfDocumentedException {
-        logger.debug("Last config snapshots to be pushed to netconf: {}", configs);
+        LOG.debug("Last config snapshots to be pushed to netconf: {}", configs);
         LinkedHashMap<ConfigSnapshotHolder, EditAndCommitResponse> result = new LinkedHashMap<>();
         // start pushing snapshots:
         for (ConfigSnapshotHolder configSnapshotHolder : configs) {
             if(configSnapshotHolder != null) {
-                EditAndCommitResponse editAndCommitResponseWithRetries = pushConfigWithConflictingVersionRetries(configSnapshotHolder);
-                logger.debug("Config snapshot pushed successfully: {}, result: {}", configSnapshotHolder, result);
+                EditAndCommitResponse editAndCommitResponseWithRetries = null;
+                try {
+                    editAndCommitResponseWithRetries = pushConfigWithConflictingVersionRetries(configSnapshotHolder);
+                } catch (ConfigSnapshotFailureException e) {
+                    LOG.warn("Failed to apply configuration snapshot: {}. Config snapshot is not semantically correct and will be IGNORED. " +
+                            "for detailed information see enclosed exception.", e.getConfigIdForReporting(), e);
+                    throw new IllegalStateException("Failed to apply configuration snapshot " + e.getConfigIdForReporting(), e);
+                }
+                LOG.debug("Config snapshot pushed successfully: {}, result: {}", configSnapshotHolder, result);
                 result.put(configSnapshotHolder, editAndCommitResponseWithRetries);
             }
         }
-        logger.debug("All configuration snapshots have been pushed successfully.");
+        LOG.debug("All configuration snapshots have been pushed successfully.");
         return result;
     }
 
@@ -123,19 +120,22 @@ public class ConfigPusherImpl implements ConfigPusher {
      * is caught, whole process is retried - new service instance need to be obtained from the factory. Closes
      * {@link NetconfOperationService} after each use.
      */
-    private synchronized EditAndCommitResponse pushConfigWithConflictingVersionRetries(ConfigSnapshotHolder configSnapshotHolder) throws NetconfDocumentedException {
+    private synchronized EditAndCommitResponse pushConfigWithConflictingVersionRetries(ConfigSnapshotHolder configSnapshotHolder) throws ConfigSnapshotFailureException {
         ConflictingVersionException lastException;
-        Stopwatch stopwatch = new Stopwatch().start();
+        Stopwatch stopwatch = Stopwatch.createUnstarted();
         do {
             String idForReporting = configSnapshotHolder.toString();
             SortedSet<String> expectedCapabilities = checkNotNull(configSnapshotHolder.getCapabilities(),
                     "Expected capabilities must not be null - %s, check %s", idForReporting,
                     configSnapshotHolder.getClass().getName());
             try (NetconfOperationService operationService = getOperationServiceWithRetries(expectedCapabilities, idForReporting)) {
+                if(!stopwatch.isRunning()) {
+                    stopwatch.start();
+                }
                 return pushConfig(configSnapshotHolder, operationService);
             } catch (ConflictingVersionException e) {
                 lastException = e;
-                logger.debug("Conflicting version detected, will retry after timeout");
+                LOG.info("Conflicting version detected, will retry after timeout");
                 sleep();
             }
         } while (stopwatch.elapsed(TimeUnit.MILLISECONDS) < conflictingVersionTimeoutMillis);
@@ -144,29 +144,80 @@ public class ConfigPusherImpl implements ConfigPusher {
     }
 
     private NetconfOperationService getOperationServiceWithRetries(Set<String> expectedCapabilities, String idForReporting) {
-        Stopwatch stopwatch = new Stopwatch().start();
-        NotEnoughCapabilitiesException lastException;
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        ConfigPusherException lastException;
         do {
             try {
                 return getOperationService(expectedCapabilities, idForReporting);
-            } catch (NotEnoughCapabilitiesException e) {
-                logger.debug("Not enough capabilities: " + e.toString());
+            } catch (ConfigPusherException e) {
+                LOG.debug("Not enough capabilities: {}", e.toString());
                 lastException = e;
                 sleep();
             }
         } while (stopwatch.elapsed(TimeUnit.MILLISECONDS) < maxWaitForCapabilitiesMillis);
-        throw new IllegalStateException("Max wait for capabilities reached." + lastException.getMessage(), lastException);
+
+        if(lastException instanceof NotEnoughCapabilitiesException) {
+            LOG.error("Unable to push configuration due to missing yang models." +
+                            " Yang models that are missing, but required by the configuration: {}." +
+                            " For each mentioned model check: " +
+                            " 1. that the mentioned yang model namespace/name/revision is identical to those in the yang model itself" +
+                            " 2. the yang file is present in the system" +
+                            " 3. the bundle with that yang file is present in the system and active" +
+                            " 4. the yang parser did not fail while attempting to parse that model",
+                    ((NotEnoughCapabilitiesException) lastException).getMissingCaps());
+            throw new IllegalStateException("Unable to push configuration due to missing yang models." +
+                    " Required yang models that are missing: "
+                    + ((NotEnoughCapabilitiesException) lastException).getMissingCaps(), lastException);
+        } else {
+            final String msg = "Unable to push configuration due to missing netconf service";
+            LOG.error(msg, lastException);
+            throw new IllegalStateException(msg, lastException);
+        }
     }
 
-    private static class NotEnoughCapabilitiesException extends Exception {
-        private static final long serialVersionUID = 1L;
+    private static class ConfigPusherException extends Exception {
+
+        public ConfigPusherException(final String message) {
+            super(message);
+        }
 
-        private NotEnoughCapabilitiesException(String message, Throwable cause) {
+        public ConfigPusherException(final String message, final Throwable cause) {
             super(message, cause);
         }
+    }
+
+    private static class NotEnoughCapabilitiesException extends ConfigPusherException {
+        private static final long serialVersionUID = 1L;
+        private Set<String> missingCaps;
 
-        private NotEnoughCapabilitiesException(String message) {
+        private NotEnoughCapabilitiesException(String message, Set<String> missingCaps) {
             super(message);
+            this.missingCaps = missingCaps;
+        }
+
+        public Set<String> getMissingCaps() {
+            return missingCaps;
+        }
+    }
+
+    private static final class NetconfServiceNotAvailableException extends ConfigPusherException {
+
+        public NetconfServiceNotAvailableException(final String s, final RuntimeException e) {
+            super(s, e);
+        }
+    }
+
+    private static final class ConfigSnapshotFailureException extends ConfigPusherException {
+
+        private final String configIdForReporting;
+
+        public ConfigSnapshotFailureException(final String configIdForReporting, final String operationNameForReporting, final Exception e) {
+            super(String.format("Failed to apply config snapshot: %s during phase: %s", configIdForReporting, operationNameForReporting), e);
+            this.configIdForReporting = configIdForReporting;
+        }
+
+        public String getConfigIdForReporting() {
+            return configIdForReporting;
         }
     }
 
@@ -177,30 +228,31 @@ public class ConfigPusherImpl implements ConfigPusher {
      * @param idForReporting
      * @return service if capabilities are present, otherwise absent value
      */
-    private NetconfOperationService getOperationService(Set<String> expectedCapabilities, String idForReporting) throws NotEnoughCapabilitiesException {
+    private NetconfOperationService getOperationService(Set<String> expectedCapabilities, String idForReporting) throws ConfigPusherException {
         NetconfOperationService serviceCandidate;
         try {
             serviceCandidate = configNetconfConnector.createService(idForReporting);
         } catch(RuntimeException e) {
-            throw new NotEnoughCapabilitiesException("Netconf service not stable for " + idForReporting, e);
+            throw new NetconfServiceNotAvailableException("Netconf service not stable for config pusher." +
+                    " Cannot push any configuration", e);
         }
-        Set<String> notFoundDiff = computeNotFoundCapabilities(expectedCapabilities, serviceCandidate);
+        Set<String> notFoundDiff = computeNotFoundCapabilities(expectedCapabilities, configNetconfConnector);
         if (notFoundDiff.isEmpty()) {
             return serviceCandidate;
         } else {
             serviceCandidate.close();
-            logger.trace("Netconf server did not provide required capabilities for {} " +
+            LOG.debug("Netconf server did not provide required capabilities for {} ", idForReporting,
                     "Expected but not found: {}, all expected {}, current {}",
-                    idForReporting, notFoundDiff, expectedCapabilities, serviceCandidate.getCapabilities()
+                     notFoundDiff, expectedCapabilities, configNetconfConnector.getCapabilities()
             );
-            throw new NotEnoughCapabilitiesException("Not enough capabilities for " + idForReporting + ". Expected but not found: " + notFoundDiff);
+            throw new NotEnoughCapabilitiesException("Not enough capabilities for " + idForReporting + ". Expected but not found: " + notFoundDiff, notFoundDiff);
         }
     }
 
-    private static Set<String> computeNotFoundCapabilities(Set<String> expectedCapabilities, NetconfOperationService serviceCandidate) {
+    private static Set<String> computeNotFoundCapabilities(Set<String> expectedCapabilities, NetconfOperationServiceFactory serviceCandidate) {
         Collection<String> actual = Collections2.transform(serviceCandidate.getCapabilities(), new Function<Capability, String>() {
             @Override
-            public String apply(Capability input) {
+            public String apply(@Nonnull final Capability input) {
                 return input.getCapabilityUri();
             }
         });
@@ -209,8 +261,6 @@ public class ConfigPusherImpl implements ConfigPusher {
         return allNotFound;
     }
 
-
-
     private void sleep() {
         try {
             Thread.sleep(100);
@@ -228,7 +278,7 @@ public class ConfigPusherImpl implements ConfigPusher {
      * @throws java.lang.RuntimeException  if edit-config or commit fails otherwise
      */
     private synchronized EditAndCommitResponse pushConfig(ConfigSnapshotHolder configSnapshotHolder, NetconfOperationService operationService)
-            throws ConflictingVersionException, NetconfDocumentedException {
+            throws ConflictingVersionException, ConfigSnapshotFailureException {
 
         Element xmlToBePersisted;
         try {
@@ -236,8 +286,8 @@ public class ConfigPusherImpl implements ConfigPusher {
         } catch (SAXException | IOException e) {
             throw new IllegalStateException("Cannot parse " + configSnapshotHolder);
         }
-        logger.trace("Pushing last configuration to netconf: {}", configSnapshotHolder);
-        Stopwatch stopwatch = new Stopwatch().start();
+        LOG.trace("Pushing last configuration to netconf: {}", configSnapshotHolder);
+        Stopwatch stopwatch = Stopwatch.createStarted();
         NetconfMessage editConfigMessage = createEditConfigMessage(xmlToBePersisted);
 
         Document editResponseMessage = sendRequestGetResponseCheckIsOK(editConfigMessage, operationService,
@@ -246,28 +296,33 @@ public class ConfigPusherImpl implements ConfigPusher {
         Document commitResponseMessage = sendRequestGetResponseCheckIsOK(getCommitMessage(), operationService,
                 "commit", configSnapshotHolder.toString());
 
-        if (logger.isTraceEnabled()) {
+        if (LOG.isTraceEnabled()) {
             StringBuilder response = new StringBuilder("editConfig response = {");
             response.append(XmlUtil.toString(editResponseMessage));
             response.append("}");
             response.append("commit response = {");
             response.append(XmlUtil.toString(commitResponseMessage));
             response.append("}");
-            logger.trace("Last configuration loaded successfully");
-            logger.trace("Detailed message {}", response);
-            logger.trace("Total time spent {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
+            LOG.trace("Last configuration loaded successfully");
+            LOG.trace("Detailed message {}", response);
+            LOG.trace("Total time spent {} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
         }
         return new EditAndCommitResponse(editResponseMessage, commitResponseMessage);
     }
 
-    private NetconfOperation findOperation(NetconfMessage request, NetconfOperationService operationService) throws NetconfDocumentedException {
+    private NetconfOperation findOperation(NetconfMessage request, NetconfOperationService operationService) {
         TreeMap<HandlingPriority, NetconfOperation> allOperations = new TreeMap<>();
         Set<NetconfOperation> netconfOperations = operationService.getNetconfOperations();
         if (netconfOperations.isEmpty()) {
             throw new IllegalStateException("Possible code error: no config operations");
         }
         for (NetconfOperation netconfOperation : netconfOperations) {
-            HandlingPriority handlingPriority = netconfOperation.canHandle(request.getDocument());
+            HandlingPriority handlingPriority = null;
+            try {
+                handlingPriority = netconfOperation.canHandle(request.getDocument());
+            } catch (NetconfDocumentedException e) {
+                throw new IllegalStateException("Possible code error: canHandle threw exception", e);
+            }
             allOperations.put(handlingPriority, netconfOperation);
         }
         Entry<HandlingPriority, NetconfOperation> highestEntry = allOperations.lastEntry();
@@ -279,24 +334,23 @@ public class ConfigPusherImpl implements ConfigPusher {
 
     private Document sendRequestGetResponseCheckIsOK(NetconfMessage request, NetconfOperationService operationService,
                                                      String operationNameForReporting, String configIdForReporting)
-            throws ConflictingVersionException, NetconfDocumentedException {
+            throws ConflictingVersionException, ConfigSnapshotFailureException {
 
         NetconfOperation operation = findOperation(request, operationService);
         Document response;
         try {
             response = operation.handle(request.getDocument(), NetconfOperationChainedExecution.EXECUTION_TERMINATION_POINT);
-        } catch (NetconfDocumentedException | RuntimeException e) {
-            if (e instanceof NetconfDocumentedException && e.getCause() instanceof ConflictingVersionException) {
+            return NetconfUtil.checkIsMessageOk(response);
+        } catch (NetconfDocumentedException e) {
+            if (e.getCause() instanceof ConflictingVersionException) {
                 throw (ConflictingVersionException) e.getCause();
             }
-            throw new IllegalStateException("Failed to send " + operationNameForReporting +
-                    " for configuration " + configIdForReporting, e);
+            throw new ConfigSnapshotFailureException(configIdForReporting, operationNameForReporting, e);
         }
-        return NetconfUtil.checkIsMessageOk(response);
     }
 
     // load editConfig.xml template, populate /rpc/edit-config/config with parameter
-    private static NetconfMessage createEditConfigMessage(Element dataElement) throws NetconfDocumentedException {
+    private static NetconfMessage createEditConfigMessage(Element dataElement) {
         String editConfigResourcePath = "/netconfOp/editConfig.xml";
         try (InputStream stream = ConfigPersisterNotificationHandler.class.getResourceAsStream(editConfigResourcePath)) {
             checkNotNull(stream, "Unable to load resource " + editConfigResourcePath);
@@ -312,7 +366,7 @@ public class ConfigPusherImpl implements ConfigPusher {
             }
             editConfigElement.appendChild(configWrapper.getDomElement());
             return new NetconfMessage(doc);
-        } catch (IOException | SAXException e) {
+        } catch (IOException | SAXException | NetconfDocumentedException e) {
             // error reading the xml file bundled into the jar
             throw new IllegalStateException("Error while opening local resource " + editConfigResourcePath, e);
         }