Bug 3822: Improve error reporting for restconf PUT 37/25337/2
authorTom Pantelis <tpanteli@brocade.com>
Sat, 20 Jun 2015 08:09:47 +0000 (04:09 -0400)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 2 Sep 2015 00:33:31 +0000 (00:33 +0000)
A runtime exception can be emitted by the netconf mount point which
should be reported to the user, otherwise you get a 500 response with
no error info which isn't very helpful.

Also the fucntionality to output the error-info field was ommitted with
the conversion from CompositeNode to NormalizedNode so I re-implemeneted
it. It was originally ommitted with a TODO b/c the
NormalizedNodeStreamWriters validate against the schema and error-info
is defined as an empty container in the restconf yang. So there's no way
to create a ContainerNode to represent the error-info data that conforms
to the schema. To work around this, I created a leaf node and special-cased
error-info in the stream writer to elide schema validation.

I also added a regression unit test for the case where the URL contains
an identityref.

Change-Id: I93b4aea25c829af1232d539180f02dd61e252d50
Signed-off-by: Tom Pantelis <tpanteli@brocade.com>
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDocumentedExceptionMapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestGetOperationTest.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/RestconfDocumentedExceptionMapperTest.java
opendaylight/md-sal/sal-rest-connector/src/test/resources/full-versions/test-module/test-module.yang

index 721864f..2e4e00d 100644 (file)
@@ -12,6 +12,7 @@ import com.google.common.base.Charsets;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
+import com.google.gson.stream.JsonWriter;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
@@ -24,6 +25,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.ExceptionMapper;
 import javax.ws.rs.ext.Provider;
+import javax.xml.XMLConstants;
 import javax.xml.stream.FactoryConfigurationError;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
@@ -35,6 +37,7 @@ import org.opendaylight.controller.sal.restconf.impl.NormalizedNodeContext;
 import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
 import org.opendaylight.controller.sal.restconf.impl.RestconfError;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -45,7 +48,9 @@ import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
 import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XMLStreamNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
@@ -188,7 +193,15 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         errNodeValues.withChild(Builders.leafBuilder((LeafSchemaNode) errMsgSchemaNode)
                 .withValue(error.getErrorMessage()).build());
 
-        // TODO : find how could we add possible "error-path" and "error-info"
+        if(error.getErrorInfo() != null) {
+            // Oddly, error-info is defined as an empty container in the restconf yang. Apparently the
+            // intention is for implementors to define their own data content so we'll just treat it as a leaf
+            // with string data.
+            errNodeValues.withChild(ImmutableNodes.leafNode(Draft02.RestConfModule.ERROR_INFO_QNAME,
+                    error.getErrorInfo()));
+        }
+
+        // TODO : find how could we add possible "error-path"
 
         return errNodeValues.build();
     }
@@ -217,8 +230,29 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         if(!schema.isAugmenting() && !(schema instanceof SchemaContext)) {
             initialNs = schema.getQName().getNamespace();
         }
-        final NormalizedNodeStreamWriter jsonWriter = JSONNormalizedNodeStreamWriter.create(context.getSchemaContext(),path,initialNs,outputWriter);
-        final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(jsonWriter);
+
+        final JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(outputWriter);
+        final NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+                JSONCodecFactory.create(context.getSchemaContext()), path, initialNs, jsonWriter);
+
+        // We create a delegating writer to special-case error-info as error-info is defined as an empty
+        // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+        // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+        // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+        // for error-info.
+        final NormalizedNodeStreamWriter streamWriter = new DelegatingNormalizedNodeStreamWriter(jsonStreamWriter) {
+            @Override
+            public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+                if(name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+                    jsonWriter.name(Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName());
+                    jsonWriter.value(value.toString());
+                } else {
+                    super.leafNode(name, value);
+                }
+            }
+        };
+
+        final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
         try {
             if(isDataRoot) {
                 writeDataRoot(outputWriter,nnWriter,(ContainerNode) data);
@@ -244,7 +278,7 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
         final InstanceIdentifierContext<?> pathContext = errorsNode.getInstanceIdentifierContext();
         final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
 
-        XMLStreamWriter xmlWriter;
+        final XMLStreamWriter xmlWriter;
         try {
             xmlWriter = XML_FACTORY.createXMLStreamWriter(outStream, "UTF-8");
         } catch (final XMLStreamException e) {
@@ -262,8 +296,33 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
             schemaPath = schemaPath.getParent();
         }
 
-        final NormalizedNodeStreamWriter streamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
+        final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
                 pathContext.getSchemaContext(), schemaPath);
+
+        // We create a delegating writer to special-case error-info as error-info is defined as an empty
+        // container in the restconf yang schema but we create a leaf node so we can output it. The delegate
+        // stream writer validates the node type against the schema and thus will expect a LeafSchemaNode but
+        // the schema has a ContainerSchemaNode so, to avoid an error, we override the leafNode behavior
+        // for error-info.
+        final NormalizedNodeStreamWriter streamWriter = new DelegatingNormalizedNodeStreamWriter(xmlStreamWriter) {
+            @Override
+            public void leafNode(final NodeIdentifier name, final Object value) throws IOException {
+                if(name.getNodeType().equals(Draft02.RestConfModule.ERROR_INFO_QNAME)) {
+                    String ns = Draft02.RestConfModule.ERROR_INFO_QNAME.getNamespace().toString();
+                    try {
+                        xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
+                                Draft02.RestConfModule.ERROR_INFO_QNAME.getLocalName(), ns);
+                        xmlWriter.writeCharacters(value.toString());
+                        xmlWriter.writeEndElement();
+                    } catch (XMLStreamException e) {
+                        throw new IOException("Error writing error-info", e);
+                    }
+                } else {
+                    super.leafNode(name, value);
+                }
+            }
+        };
+
         final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(streamWriter);
         try {
             if (isDataRoot) {
@@ -309,4 +368,94 @@ public class RestconfDocumentedExceptionMapper implements ExceptionMapper<Restco
             nnWriter.flush();
         }
     }
+
+    private static class DelegatingNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter {
+        private final NormalizedNodeStreamWriter delegate;
+
+        DelegatingNormalizedNodeStreamWriter(NormalizedNodeStreamWriter delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void leafNode(NodeIdentifier name, Object value) throws IOException, IllegalArgumentException {
+            delegate.leafNode(name, value);
+        }
+
+        @Override
+        public void startLeafSet(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
+            delegate.startLeafSet(name, childSizeHint);
+        }
+
+        @Override
+        public void leafSetEntryNode(Object value) throws IOException, IllegalArgumentException {
+            delegate.leafSetEntryNode(value);
+        }
+
+        @Override
+        public void startContainerNode(NodeIdentifier name, int childSizeHint) throws IOException,
+                IllegalArgumentException {
+            delegate.startContainerNode(name, childSizeHint);
+        }
+
+        @Override
+        public void startUnkeyedList(NodeIdentifier name, int childSizeHint) throws IOException,
+                IllegalArgumentException {
+            delegate.startUnkeyedList(name, childSizeHint);
+        }
+
+        @Override
+        public void startUnkeyedListItem(NodeIdentifier name, int childSizeHint) throws IOException,
+                IllegalStateException {
+            delegate.startUnkeyedListItem(name, childSizeHint);
+        }
+
+        @Override
+        public void startMapNode(NodeIdentifier name, int childSizeHint) throws IOException, IllegalArgumentException {
+            delegate.startMapNode(name, childSizeHint);
+        }
+
+        @Override
+        public void startMapEntryNode(NodeIdentifierWithPredicates identifier, int childSizeHint) throws IOException,
+                IllegalArgumentException {
+            delegate.startMapEntryNode(identifier, childSizeHint);
+        }
+
+        @Override
+        public void startOrderedMapNode(NodeIdentifier name, int childSizeHint) throws IOException,
+                IllegalArgumentException {
+            delegate.startOrderedMapNode(name, childSizeHint);
+        }
+
+        @Override
+        public void startChoiceNode(NodeIdentifier name, int childSizeHint) throws IOException,
+                IllegalArgumentException {
+            delegate.startChoiceNode(name, childSizeHint);
+        }
+
+        @Override
+        public void startAugmentationNode(AugmentationIdentifier identifier) throws IOException,
+                IllegalArgumentException {
+            delegate.startAugmentationNode(identifier);
+        }
+
+        @Override
+        public void anyxmlNode(NodeIdentifier name, Object value) throws IOException, IllegalArgumentException {
+            delegate.anyxmlNode(name, value);
+        }
+
+        @Override
+        public void endNode() throws IOException, IllegalStateException {
+            delegate.endNode();
+        }
+
+        @Override
+        public void close() throws IOException {
+            delegate.close();
+        }
+
+        @Override
+        public void flush() throws IOException {
+            delegate.flush();
+        }
+    }
 }
index 7864370..bc84832 100644 (file)
@@ -747,6 +747,10 @@ public class RestconfImpl implements RestconfService {
                     LOG.debug("Update ConfigDataStore fail " + identifier, e);
                     throw new RestconfDocumentedException(e.getMessage(), e, e.getErrorList());
                 }
+            } catch (Exception e) {
+                final String errMsg = "Error updating data ";
+                LOG.debug(errMsg + identifier, e);
+                throw new RestconfDocumentedException(errMsg, e);
             }
         }
 
@@ -881,7 +885,7 @@ public class RestconfImpl implements RestconfService {
             throw e;
         } catch (final Exception e) {
             final String errMsg = "Error creating data ";
-            LOG.info(errMsg + uriInfo.getPath(), e);
+            LOG.info(errMsg + (uriInfo != null ? uriInfo.getPath() : ""), e);
             throw new RestconfDocumentedException(errMsg, e);
         }
 
index eb7ea71..cbc8e27 100644 (file)
@@ -18,6 +18,7 @@ import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import java.io.FileNotFoundException;
 import java.io.UnsupportedEncodingException;
@@ -61,12 +62,15 @@ import org.opendaylight.controller.sal.restconf.impl.RestconfImpl;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapEntryNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -194,8 +198,7 @@ public class RestGetOperationTest extends JerseyTest {
      * @throws ParseException
      */
     @Test
-    public void getDataWithSlashesBehindMountPoint() throws UnsupportedEncodingException, URISyntaxException,
-            ParseException {
+    public void getDataWithSlashesBehindMountPoint() throws Exception {
         final YangInstanceIdentifier awaitedInstanceIdentifier = prepareInstanceIdentifierForList();
         when(brokerFacade.readConfigurationData(any(DOMMountPoint.class), eq(awaitedInstanceIdentifier))).thenReturn(
                 prepareCnDataForSlashesBehindMountPointTest());
@@ -210,14 +213,12 @@ public class RestGetOperationTest extends JerseyTest {
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
     }
 
-    private YangInstanceIdentifier prepareInstanceIdentifierForList() throws URISyntaxException, ParseException {
+    private YangInstanceIdentifier prepareInstanceIdentifierForList() throws Exception {
         final List<PathArgument> parameters = new ArrayList<>();
 
-        final Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
-        final URI uri = new URI("test:module");
-        final QName qNameCont = QName.create(uri, revision, "cont");
-        final QName qNameList = QName.create(uri, revision, "lst1");
-        final QName qNameKeyList = QName.create(uri, revision, "lf11");
+        final QName qNameCont = newTestModuleQName("cont");
+        final QName qNameList = newTestModuleQName("lst1");
+        final QName qNameKeyList = newTestModuleQName("lf11");
 
         parameters.add(new YangInstanceIdentifier.NodeIdentifier(qNameCont));
         parameters.add(new YangInstanceIdentifier.NodeIdentifier(qNameList));
@@ -226,6 +227,12 @@ public class RestGetOperationTest extends JerseyTest {
         return YangInstanceIdentifier.create(parameters);
     }
 
+    private QName newTestModuleQName(String localPart) throws Exception {
+        final Date revision = new SimpleDateFormat("yyyy-MM-dd").parse("2014-01-09");
+        final URI uri = new URI("test:module");
+        return QName.create(uri, revision, localPart);
+    }
+
     @Test
     public void getDataMountPointIntoHighestElement() throws UnsupportedEncodingException, URISyntaxException,
             ParseException {
@@ -242,6 +249,30 @@ public class RestGetOperationTest extends JerseyTest {
         assertEquals(200, get(uri, MediaType.APPLICATION_XML));
     }
 
+    @SuppressWarnings("unchecked")
+    @Test
+    public void getDataWithIdentityrefInURL() throws Exception {
+        setControllerContext(schemaContextTestModule);
+
+        QName moduleQN = newTestModuleQName("module");
+        ImmutableMap<QName, Object> keyMap = ImmutableMap.<QName, Object>builder()
+                .put(newTestModuleQName("type"), newTestModuleQName("test-identity"))
+                .put(newTestModuleQName("name"), "foo").build();
+        YangInstanceIdentifier iid = YangInstanceIdentifier.builder().node(newTestModuleQName("modules"))
+                .node(moduleQN).nodeWithKey(moduleQN, keyMap).build();
+        @SuppressWarnings("rawtypes")
+        NormalizedNode data = ImmutableMapNodeBuilder.create().withNodeIdentifier(
+                new NodeIdentifier(moduleQN)).withChild(ImmutableNodes.mapEntryBuilder()
+                        .withNodeIdentifier(new NodeIdentifierWithPredicates(moduleQN, keyMap))
+                        .withChild(ImmutableNodes.leafNode(newTestModuleQName("type"), newTestModuleQName("test-identity")))
+                        .withChild(ImmutableNodes.leafNode(newTestModuleQName("name"), "foo"))
+                        .withChild(ImmutableNodes.leafNode(newTestModuleQName("data"), "bar")).build()).build();
+        when(brokerFacade.readConfigurationData(iid)).thenReturn(data);
+
+        String uri = "/config/test-module:modules/module/test-module:test-identity/foo";
+        assertEquals(200, get(uri, MediaType.APPLICATION_XML));
+    }
+
     // /modules
     @Test
     public void getModulesTest() throws UnsupportedEncodingException, FileNotFoundException {
index 0244aa7..9edd952 100644 (file)
@@ -18,7 +18,6 @@ import static org.mockito.Matchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.io.ByteStreams;
 import com.google.gson.JsonArray;
@@ -68,7 +67,6 @@ import org.opendaylight.controller.sal.restconf.impl.RestconfError;
 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
 import org.w3c.dom.Document;
-import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 
@@ -85,60 +83,6 @@ public class RestconfDocumentedExceptionMapperTest extends JerseyTest {
         void verifyJson(JsonElement errorInfoElement);
     }
 
-    static class ComplexErrorInfoVerifier implements ErrorInfoVerifier {
-
-        Map<String, String> expErrorInfo;
-
-        public ComplexErrorInfoVerifier(final Map<String, String> expErrorInfo) {
-            this.expErrorInfo = expErrorInfo;
-        }
-
-        @Override
-        public void verifyXML(final Node errorInfoNode) {
-
-            final Map<String, String> mutableExpMap = Maps.newHashMap(expErrorInfo);
-            final NodeList childNodes = errorInfoNode.getChildNodes();
-            for (int i = 0; i < childNodes.getLength(); i++) {
-                final Node child = childNodes.item(i);
-                if (child instanceof Element) {
-                    final String expValue = mutableExpMap.remove(child.getNodeName());
-                    assertNotNull("Found unexpected \"error-info\" child node: " + child.getNodeName(), expValue);
-                    assertEquals("Text content for \"error-info\" child node " + child.getNodeName(), expValue,
-                            child.getTextContent());
-                }
-            }
-
-            if (!mutableExpMap.isEmpty()) {
-                fail("Missing \"error-info\" child nodes: " + mutableExpMap);
-            }
-        }
-
-        @Override
-        public void verifyJson(final JsonElement errorInfoElement) {
-
-            assertTrue("\"error-info\" Json element is not an Object", errorInfoElement.isJsonObject());
-
-            final Map<String, String> actualErrorInfo = Maps.newHashMap();
-            for (final Entry<String, JsonElement> entry : errorInfoElement.getAsJsonObject().entrySet()) {
-                final String leafName = entry.getKey();
-                final JsonElement leafElement = entry.getValue();
-                actualErrorInfo.put(leafName, leafElement.getAsString());
-            }
-
-            final Map<String, String> mutableExpMap = Maps.newHashMap(expErrorInfo);
-            for (final Entry<String, String> actual : actualErrorInfo.entrySet()) {
-                final String expValue = mutableExpMap.remove(actual.getKey());
-                assertNotNull("Found unexpected \"error-info\" child node: " + actual.getKey(), expValue);
-                assertEquals("Text content for \"error-info\" child node " + actual.getKey(), expValue,
-                        actual.getValue());
-            }
-
-            if (!mutableExpMap.isEmpty()) {
-                fail("Missing \"error-info\" child nodes: " + mutableExpMap);
-            }
-        }
-    }
-
     static class SimpleErrorInfoVerifier implements ErrorInfoVerifier {
 
         String expTextContent;
@@ -432,18 +376,16 @@ public class RestconfDocumentedExceptionMapperTest extends JerseyTest {
     }
 
     @Test
-    @Ignore // TODO : we are not supported "error-info" element yet
     public void testToJsonResponseWithErrorInfo() throws Exception {
 
         final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
         testJsonResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
                 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
-                new ComplexErrorInfoVerifier(ImmutableMap.of("session-id", "123", "address", "1.2.3.4")));
+                new SimpleErrorInfoVerifier(errorInfo));
     }
 
     @Test
-    @Ignore //TODO : we are not supporting "error-info" yet
     public void testToJsonResponseWithExceptionCause() throws Exception {
 
         final Exception cause = new Exception("mock exception cause");
@@ -634,18 +576,16 @@ public class RestconfDocumentedExceptionMapperTest extends JerseyTest {
     }
 
     @Test
-    @Ignore // TODO : we are not supporting "error-info" node yet
     public void testToXMLResponseWithErrorInfo() throws Exception {
 
         final String errorInfo = "<address>1.2.3.4</address> <session-id>123</session-id>";
         testXMLResponse(new RestconfDocumentedException(new RestconfError(ErrorType.APPLICATION,
                 ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag", errorInfo)), Status.BAD_REQUEST,
                 ErrorType.APPLICATION, ErrorTag.INVALID_VALUE, "mock error", "mock-app-tag",
-                new ComplexErrorInfoVerifier(ImmutableMap.of("session-id", "123", "address", "1.2.3.4")));
+                new SimpleErrorInfoVerifier(errorInfo));
     }
 
     @Test
-    @Ignore // TODO : we are not supporting "error-info" node yet
     public void testToXMLResponseWithExceptionCause() throws Exception {
 
         final Exception cause = new Exception("mock exception cause");
index efc5e2c..2cc78b3 100644 (file)
@@ -5,6 +5,12 @@ module test-module {
   revision 2014-01-09 {
   }
 
+  identity module-type {
+  }
+
+  identity test-identity {
+  }
+
   container interfaces {
     container class {
         leaf name {
@@ -35,7 +41,28 @@ module test-module {
         }
     }
   }
-  
+
+  container modules {
+      list module {
+          key "type name";
+          leaf name {
+              type string;
+              mandatory true;
+          }
+
+          leaf type {
+              type identityref {
+                  base module-type;
+              }
+              mandatory true;
+          }
+
+          leaf data {
+              type string;
+          }
+      }
+  }
+
   list lst-with-composite-key {
     key "key1 key2";
     leaf key1 {

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.