Merge "Finding data nodes in choices and cases."
authorEd Warnicke <eaw@cisco.com>
Fri, 29 Nov 2013 14:20:34 +0000 (14:20 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Fri, 29 Nov 2013 14:20:34 +0000 (14:20 +0000)
opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/osgi/BlankTransactionServiceTracker.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/CompositeNodeWrapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NodeWrapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/SimpleNodeWrapper.java
opendaylight/netconf/config-persister-impl/pom.xml
opendaylight/netconf/config-persister-impl/src/main/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandler.java
opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandlerTest.java [new file with mode: 0644]
opendaylight/netconf/config-persister-impl/src/test/resources/conflictingVersionResponse.xml [new file with mode: 0644]

index de1a425ce64813f5c783c710367cee51232e66fc..3d0decb93d3c98d5238de6bfc856906147141151 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.controller.config.manager.impl.osgi;
 
+import org.opendaylight.controller.config.api.ConflictingVersionException;
 import org.opendaylight.controller.config.api.jmx.CommitStatus;
 import org.opendaylight.controller.config.manager.impl.ConfigRegistryImpl;
 import org.opendaylight.controller.config.spi.ModuleFactory;
@@ -19,7 +20,7 @@ import javax.management.ObjectName;
 
 /**
  * Every time factory is added or removed, blank transaction is triggered to handle
- * {@link org.opendaylight.controller.config.spi.ModuleFactory#getDefaultModules(org.opendaylight.controller.config.api.DependencyResolverFactory)}
+ * {@link org.opendaylight.controller.config.spi.ModuleFactory#getDefaultModules(org.opendaylight.controller.config.api.DependencyResolverFactory, org.osgi.framework.BundleContext)}
  * functionality.
  */
 public class BlankTransactionServiceTracker implements ServiceTrackerCustomizer<ModuleFactory, Object> {
@@ -38,14 +39,30 @@ public class BlankTransactionServiceTracker implements ServiceTrackerCustomizer<
     }
 
     private synchronized void blankTransaction() {
-        // create transaction
-        ObjectName tx = configRegistry.beginConfig();
-        CommitStatus commitStatus = configRegistry.commitConfig(tx);
-        logger.debug("Committed blank transaction with status {}", commitStatus);
+        // race condition check: config-persister might push new configuration while server is starting up.
+        ConflictingVersionException lastException = null;
+        for (int i = 0; i < 10; i++) {
+            try {
+                // create transaction
+                ObjectName tx = configRegistry.beginConfig();
+                CommitStatus commitStatus = configRegistry.commitConfig(tx);
+                logger.debug("Committed blank transaction with status {}", commitStatus);
+                return;
+            } catch (ConflictingVersionException e) {
+                lastException = e;
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException interruptedException) {
+                    Thread.currentThread().interrupt();
+                    throw new IllegalStateException(interruptedException);
+                }
+            }
+        }
+        throw lastException;
     }
 
     @Override
-    public void modifiedService(ServiceReference<ModuleFactory> moduleFactoryServiceReference, Object o) {
+    public void modifiedService(ServiceReference <ModuleFactory> moduleFactoryServiceReference, Object o) {
         blankTransaction();
     }
 
index 29ad7522c43436891ec6cb717ee6cbc713a04abd..1b0fda41ca723d67f640de2418ebd6e33e153751 100644 (file)
@@ -1,6 +1,5 @@
 package org.opendaylight.controller.sal.restconf.impl
 
-import javax.ws.rs.WebApplicationException
 import javax.ws.rs.core.Response
 import org.opendaylight.controller.md.sal.common.api.data.DataReader
 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession
index e000c7e29e21c0a39904088351940d6862e3f42d..c741c9aa19cbc1d6c27db7734aa79cb9d39b00de 100644 (file)
@@ -24,6 +24,7 @@ public final class CompositeNodeWrapper implements NodeWrapper<CompositeNode>, C
 
     private String localName;
     private URI namespace;
+    private QName name;
     private List<NodeWrapper<?>> values = new ArrayList<>();
     
     public CompositeNodeWrapper(String localName) {
@@ -34,6 +35,12 @@ public final class CompositeNodeWrapper implements NodeWrapper<CompositeNode>, C
         this(localName);
         this.namespace = namespace;
     }
+    
+    @Override
+    public void setQname(QName name) {
+        Preconditions.checkState(compositeNode == null, "Cannot change the object, due to data inconsistencies.");
+        this.name = name;
+    }
 
     @Override
     public String getLocalName() {
@@ -75,9 +82,11 @@ public final class CompositeNodeWrapper implements NodeWrapper<CompositeNode>, C
     @Override
     public CompositeNode unwrap(CompositeNode parent) {
         if (compositeNode == null) {
-            Preconditions.checkNotNull(namespace);
-            compositeNode = NodeFactory.createMutableCompositeNode(new QName(namespace, localName), 
-                    parent, new ArrayList<Node<?>>(), ModifyAction.CREATE, null);
+            if (name == null) {
+                Preconditions.checkNotNull(namespace);
+                name = new QName(namespace, localName);
+            }
+            compositeNode = NodeFactory.createMutableCompositeNode(name, parent, new ArrayList<Node<?>>(), null, null);
             
             List<Node<?>> nodeValues = new ArrayList<>();
             for (NodeWrapper<?> nodeWrapper : values) {
@@ -88,6 +97,7 @@ public final class CompositeNodeWrapper implements NodeWrapper<CompositeNode>, C
             values = null;
             namespace = null;
             localName = null;
+            name = null;
         }
         return compositeNode;
     }
index 065d01e8e9405058f563189e6f5117d30eed4857..882c73d001071dea11ee8bfb1d253887780a0875 100644 (file)
@@ -128,6 +128,25 @@ class ControllerContext implements SchemaServiceListener {
     private def dispatch CharSequence toRestconfIdentifier(PathArgument argument, DataSchemaNode node) {
         throw new IllegalArgumentException("Conversion of generic path argument is not supported");
     }
+    
+    def findModuleByNamespace(URI namespace) {
+        checkPreconditions
+        var module = uriToModuleName.get(namespace)
+        if (module === null) {
+            val moduleSchemas = schemas.findModuleByNamespace(namespace);
+            if(moduleSchemas === null) throw new IllegalArgumentException()
+            var latestModule = moduleSchemas.head
+            for (m : moduleSchemas) {
+                if (m.revision.after(latestModule.revision)) {
+                    latestModule = m
+                }
+            }
+            if(latestModule === null) throw new IllegalArgumentException()
+            uriToModuleName.put(namespace, latestModule.name)
+            module = latestModule.name;
+        }
+        return module
+    }
 
     def CharSequence toRestconfIdentifier(QName qname) {
         checkPreconditions
index 0a3616e494a22527f3d95d7294daccf733181bd4..db7770fc68797504dc969a89eaddda832aa5c1d4 100644 (file)
@@ -2,11 +2,14 @@ package org.opendaylight.controller.sal.restconf.impl;
 
 import java.net.URI;
 
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.opendaylight.yangtools.yang.data.api.Node;
 
 public interface NodeWrapper<T extends Node<?>> {
 
+    void setQname(QName name);
+    
     T unwrap(CompositeNode parent);
     
     URI getNamespace();
index b6426c65bfdd55c8f984a8433eaddbb9ccb20dbc..b7307544397b06e4bf544a71b0fd59471cfa72fd 100644 (file)
@@ -1,10 +1,12 @@
 package org.opendaylight.controller.sal.restconf.impl
 
 import java.util.List
+import java.util.Set
 import javax.ws.rs.core.Response
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus
 import org.opendaylight.controller.sal.rest.api.RestconfService
 import org.opendaylight.yangtools.yang.data.api.CompositeNode
+import org.opendaylight.yangtools.yang.model.api.ChoiceNode
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode
 
@@ -77,7 +79,7 @@ class RestconfImpl implements RestconfService {
     
     override readConfigurationData(String identifier) {
         val instanceIdentifierWithSchemaNode = identifier.resolveInstanceIdentifier
-        val data = broker.readOperationalData(instanceIdentifierWithSchemaNode.getInstanceIdentifier);
+        val data = broker.readConfigurationData(instanceIdentifierWithSchemaNode.getInstanceIdentifier);
         return new StructuredData(data, instanceIdentifierWithSchemaNode.schemaNode)
     }
     
@@ -112,16 +114,43 @@ class RestconfImpl implements RestconfService {
     }
 
     private def void addNamespaceToNodeFromSchemaRecursively(NodeWrapper<?> nodeBuilder, DataSchemaNode schema) {
-        if (nodeBuilder.namespace === null) {
-            nodeBuilder.namespace = schema.QName.namespace
+        if (schema === null) {
+            throw new ResponseException(Response.Status.BAD_REQUEST,
+                "Data has bad format\n" + nodeBuilder.localName + " does not exist in yang schema.");
+        }
+        val moduleName = controllerContext.findModuleByNamespace(schema.QName.namespace);
+        if (nodeBuilder.namespace === null || nodeBuilder.namespace == schema.QName.namespace ||
+            nodeBuilder.namespace.path == moduleName) {
+            nodeBuilder.qname = schema.QName
+        } else {
+            throw new ResponseException(Response.Status.BAD_REQUEST,
+                "Data has bad format\nIf data is in XML format then namespace for " + nodeBuilder.localName +
+                    " should be " + schema.QName.namespace + ".\n If data is in Json format then module name for " +
+                    nodeBuilder.localName + " should be " + moduleName + ".");
         }
         if (nodeBuilder instanceof CompositeNodeWrapper) {
             val List<NodeWrapper<?>> children = (nodeBuilder as CompositeNodeWrapper).getValues
             for (child : children) {
                 addNamespaceToNodeFromSchemaRecursively(child,
-                    (schema as DataNodeContainer).childNodes.findFirst[n|n.QName.localName.equals(child.localName)])
+                    findFirstSchemaByLocalName(child.localName, (schema as DataNodeContainer).childNodes))
+            }
+        }
+    }
+    
+    private def DataSchemaNode findFirstSchemaByLocalName(String localName, Set<DataSchemaNode> schemas) {
+        for (schema : schemas) {
+            if (schema instanceof ChoiceNode) {
+                for (caze : (schema as ChoiceNode).cases) {
+                    val result =  findFirstSchemaByLocalName(localName, caze.childNodes)
+                    if (result !== null) {
+                        return result
+                    }
+                }
+            } else {
+                return schemas.findFirst[n|n.QName.localName.equals(localName)]
             }
         }
+        return null
     }
 
 }
index 4600d0890bcc41f30f6f43ff0907c978156352a3..1b103b43c25219f60a482fcc7b9a459666ece55f 100644 (file)
@@ -18,6 +18,7 @@ public final class SimpleNodeWrapper implements NodeWrapper<SimpleNode<?>>, Simp
     private String localName;
     private String value;
     private URI namespace;
+    private QName name;
 
     public SimpleNodeWrapper(String localName, String value) {
         this.localName = Preconditions.checkNotNull(localName);
@@ -29,6 +30,12 @@ public final class SimpleNodeWrapper implements NodeWrapper<SimpleNode<?>>, Simp
         this.namespace = namespace;
     }
     
+    @Override
+    public void setQname(QName name) {
+        Preconditions.checkState(simpleNode == null, "Cannot change the object, due to data inconsistencies.");
+        this.name = name;
+    }
+    
     @Override
     public String getLocalName() {
         if (simpleNode != null) {
@@ -54,12 +61,16 @@ public final class SimpleNodeWrapper implements NodeWrapper<SimpleNode<?>>, Simp
     @Override
     public SimpleNode<String> unwrap(CompositeNode parent) {
         if (simpleNode == null) {
-            Preconditions.checkNotNull(namespace);
-            simpleNode = NodeFactory.createImmutableSimpleNode(new QName(namespace, localName), parent, value);
+            if (name == null) {
+                Preconditions.checkNotNull(namespace);
+                name = new QName(namespace, localName);
+            }
+            simpleNode = NodeFactory.createImmutableSimpleNode(name, parent, value);
             
             value = null;
             namespace = null;
             localName = null;
+            name = null;
         }
         return simpleNode;
     }
index ce25de215e69d3a285d819e8ad3354f8fbdb91b3..dce858fce63dbad06159d7109f77fb40005c301e 100644 (file)
@@ -89,6 +89,9 @@
                             org.slf4j,
                             org.w3c.dom,
                             org.xml.sax,
+                            javax.xml.namespace,
+                            javax.xml.xpath,
+                            org.opendaylight.controller.config.api
                         </Import-Package>
                         <Export-Package>
                         </Export-Package>
index a569f90538104048026249370a6650119007f32f..a777ea4099e5bf2595f57b512618551c4c4d2d68 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,16 +100,25 @@ public class ConfigPersisterNotificationHandler implements NotificationListener,
 
         if (maybeConfig.isPresent()) {
             logger.debug("Last config found {}", persister);
-
-            registerToNetconf(maybeConfig.get().getCapabilities());
-
-            final String configSnapshot = maybeConfig.get().getConfigSnapshot();
-            logger.trace("Pushing following xml to netconf {}", configSnapshot);
-            try {
-                pushLastConfig(XmlUtil.readXmlToElement(configSnapshot));
-            } catch (SAXException | IOException e) {
-                throw new IllegalStateException("Unable to load last config", e);
+            ConflictingVersionException lastException = null;
+            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));
+                } catch(ConflictingVersionException e) {
+                    closeClientAndDispatcher(netconfClient, netconfClientDispatcher);
+                    lastException = e;
+                    Thread.sleep(1000);
+                } catch (SAXException | IOException e) {
+                    throw new IllegalStateException("Unable to load last config", e);
+                }
             }
+            throw new IllegalStateException("Failed to push configuration, maximum attempt count has been reached: "
+                    + maxAttempts, lastException);
 
         } else {
             // this ensures that netconf is initialized, this is first
@@ -130,9 +143,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 +159,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);
 
@@ -245,7 +256,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 +287,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) {
diff --git a/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandlerTest.java b/opendaylight/netconf/config-persister-impl/src/test/java/org/opendaylight/controller/netconf/persist/impl/ConfigPersisterNotificationHandlerTest.java
new file mode 100644 (file)
index 0000000..6c45c9c
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.netconf.persist.impl;
+
+import org.junit.Test;
+import org.opendaylight.controller.config.api.ConflictingVersionException;
+import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.xml.XmlElement;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.w3c.dom.Document;
+
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+public class ConfigPersisterNotificationHandlerTest {
+
+    @Test
+    public void testConflictingVersionDetection() throws Exception {
+        Document document = XmlUtil.readXmlToDocument(getClass().getResourceAsStream("/conflictingVersionResponse.xml"));
+        try{
+            ConfigPersisterNotificationHandler.checkIsOk(XmlElement.fromDomDocument(document).getOnlyChildElement(), new NetconfMessage(document));
+            fail();
+        }catch(ConflictingVersionException e){
+            assertThat(e.getMessage(), containsString("Optimistic lock failed. Expected parent version 21, was 18"));
+        }
+    }
+
+}
diff --git a/opendaylight/netconf/config-persister-impl/src/test/resources/conflictingVersionResponse.xml b/opendaylight/netconf/config-persister-impl/src/test/resources/conflictingVersionResponse.xml
new file mode 100644 (file)
index 0000000..f65e0e0
--- /dev/null
@@ -0,0 +1,13 @@
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="persister_commit">
+    <rpc-error>
+        <error-type>application</error-type>
+        <error-tag>operation-failed</error-tag>
+        <error-severity>error</error-severity>
+
+
+
+        <error-info>
+            <error>org.opendaylight.controller.config.api.ConflictingVersionException: Optimistic lock failed. Expected parent version 21, was 18</error>
+        </error-info>
+    </rpc-error>
+</rpc-reply>