Unit test for PostDataTransactionUtil class 26/47126/2
authorIvan Hrasko <ivan.hrasko@pantheon.tech>
Wed, 19 Oct 2016 12:36:58 +0000 (14:36 +0200)
committerIvan Hrasko <ivan.hrasko@pantheon.tech>
Wed, 19 Oct 2016 13:18:07 +0000 (13:18 +0000)
Change-Id: I38e1d29daa1598609e1a706b16d1bb2f24012f0f
Signed-off-by: miroslav.kovac <miroslav.kovac@pantheon.tech>
Signed-off-by: Ivan Hrasko <ivan.hrasko@pantheon.tech>
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureCallbackTx.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureDataFactory.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ResponseFactory.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtilTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/jukebox/jukebox@2015-04-04.yang [new file with mode: 0644]

index 01760376b95ba9c037685f3a99deb34f78044568..e7829c3f89f0e6f43fddd9c9dc784bc1b5693aae 100644 (file)
@@ -90,6 +90,7 @@ final class FutureCallbackTx {
     protected static <T> void handlingLoggerAndValues(@Nullable final Throwable t, final String txType,
             final T result, final FutureDataFactory<T> dataFactory) {
         if (t != null) {
+            dataFactory.setFailureStatus();
             LOG.warn("Transaction({}) FAILED!", txType, t);
             if (t instanceof DOMRpcException) {
                 final List<RpcError> rpcErrorList = new ArrayList<>();
index bec46374d3e362baa2e5c9913edd7d33b8c2c53f..c6373bc6b60f118300b9260fb69d2e72dd9b3258 100644 (file)
@@ -10,9 +10,18 @@ package org.opendaylight.restconf.restful.utils;
 class FutureDataFactory<T> {
 
     protected T result;
+    private boolean statusFail = false;
 
     void setResult(final T result) {
         this.result = result;
     }
 
+    void setFailureStatus() {
+        this.statusFail = true;
+    }
+
+    boolean getFailureStatus() {
+        return statusFail;
+    }
+
 }
index f507837630e83d2c0b5b836130a203dba85187a4..fc9c676c4ea36a4bcaeb60bd591765d019a57813 100644 (file)
@@ -17,6 +17,9 @@ import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFaile
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
 import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.RestConnectorProvider;
 import org.opendaylight.restconf.common.references.SchemaContextRef;
 import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
 import org.opendaylight.restconf.utils.parser.ParserIdentifier;
@@ -67,9 +70,7 @@ public final class PostDataTransactionUtil {
                 payload.getInstanceIdentifierContext().getInstanceIdentifier(), payload.getData(),
                 transactionNode, schemaContextRef.get());
         final URI location = PostDataTransactionUtil.resolveLocation(uriInfo, transactionNode, schemaContextRef);
-        final ResponseFactory dataFactory = new ResponseFactory(
-                ReadDataTransactionUtil.readData(RestconfDataServiceConstant.ReadData.CONFIG, transactionNode),
-                location);
+        final ResponseFactory dataFactory = new ResponseFactory(null, location);
         FutureCallbackTx.addCallback(future, RestconfDataServiceConstant.PostData.POST_TX_TYPE, dataFactory);
         return dataFactory.build();
     }
@@ -116,6 +117,14 @@ public final class PostDataTransactionUtil {
             for (final DataContainerChild<? extends PathArgument, ?> child : ((ContainerNode) data).getValue()) {
                 putChild(child, transactionChain, transaction, path);
             }
+        } else {
+            transaction.cancel();
+            RestConnectorProvider.resetTransactionChainForAdapaters(transactionChain);
+
+            final String errMsg = "Only Map, Choice, Augmentation, LeafSet and Container nodes are supported";
+            LOG.trace("{}:{}", errMsg, path);
+            throw new RestconfDocumentedException(
+                    "Node not supported", RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.BAD_ELEMENT, path);
         }
 
         return transaction.submit();
index 5d8c4f56aa63124915eebf5755c37c179f99f88e..b4137ac989eb1df02e2154714606cc5b700ad845 100644 (file)
@@ -16,7 +16,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
 final class ResponseFactory extends FutureDataFactory<Void> implements Builder<Response> {
 
-    private final ResponseBuilder responseBuilder;
+    private ResponseBuilder responseBuilder;
     ResponseFactory(final NormalizedNode<?, ?> readData) {
         final Status status = prepareStatus(readData);
         this.responseBuilder = Response.status(status);
@@ -34,6 +34,9 @@ final class ResponseFactory extends FutureDataFactory<Void> implements Builder<R
 
     @Override
     public Response build() {
+        if (getFailureStatus()) {
+            responseBuilder = responseBuilder.status(Response.Status.INTERNAL_SERVER_ERROR);
+        }
         return this.responseBuilder.build();
     }
 
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtilTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtilTest.java
new file mode 100644 (file)
index 0000000..8c1a81c
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2016 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.restconf.restful.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.base.Optional;
+import com.google.common.util.concurrent.Futures;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.NormalizedNodeContext;
+import org.opendaylight.restconf.common.references.SchemaContextRef;
+import org.opendaylight.restconf.restful.transaction.TransactionVarsWrapper;
+import org.opendaylight.yangtools.util.SingletonSet;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+import org.w3c.dom.DOMException;
+
+public class PostDataTransactionUtilTest {
+
+    private static final String PATH_FOR_NEW_SCHEMA_CONTEXT = "/jukebox";
+
+    @Mock
+    private DOMTransactionChain transactionChain;
+    @Mock
+    private DOMDataReadWriteTransaction readWrite;
+    @Mock
+    private DOMDataReadOnlyTransaction read;
+    @Mock
+    private DOMDataWriteTransaction write;
+    @Mock
+    private UriInfo uriInfo;
+    @Mock
+    private UriBuilder uriBuilder;
+
+    private SchemaContextRef refSchemaCtx;
+    private ContainerNode buildBaseCont;
+    private SchemaContext schema;
+    private YangInstanceIdentifier iid2;
+    private MapNode buildList;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        refSchemaCtx = new SchemaContextRef(TestRestconfUtils.loadSchemaContext(PATH_FOR_NEW_SCHEMA_CONTEXT));
+        schema = refSchemaCtx.get();
+
+        final QName baseQName = QName.create("http://example.com/ns/example-jukebox", "2015-04-04", "jukebox");
+        final QName containerQname = QName.create(baseQName, "player");
+        final QName leafQname = QName.create(baseQName, "gap");
+        final QName listQname = QName.create(baseQName, "playlist");
+        final QName listKeyQname = QName.create(baseQName, "name");
+        final YangInstanceIdentifier.NodeIdentifierWithPredicates nodeWithKey =
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(listQname, listKeyQname, "name of band");
+        iid2 = YangInstanceIdentifier.builder()
+                .node(baseQName)
+                .build();
+
+        final LeafNode buildLeaf = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(leafQname))
+                .withValue(0.2)
+                .build();
+        final ContainerNode buildPlayerCont = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(containerQname))
+                .withChild(buildLeaf)
+                .build();
+        buildBaseCont = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(baseQName))
+                .withChild(buildPlayerCont)
+                .build();
+
+        final LeafNode<Object> content = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "name")))
+                .withValue("name of band")
+                .build();
+        final LeafNode<Object> content2 = Builders.leafBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "description")))
+                .withValue("band description")
+                .build();
+        final MapEntryNode mapEntryNode = Builders.mapEntryBuilder()
+                .withNodeIdentifier(nodeWithKey)
+                .withChild(content)
+                .withChild(content2)
+                .build();
+        buildList = Builders.mapBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(listQname))
+                .withChild(mapEntryNode)
+                .build();
+
+        doReturn(UriBuilder.fromUri("http://localhost:8181/restconf/15/")).when(uriInfo).getBaseUriBuilder();
+        doReturn(readWrite).when(transactionChain).newReadWriteTransaction();
+        doReturn(read).when(transactionChain).newReadOnlyTransaction();
+    }
+
+    @Test
+    public void testPostContainerData() {
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext = new InstanceIdentifierContext<>(iid2, null, null, schema);
+        final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, buildBaseCont);
+
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(read).read(LogicalDatastoreType.CONFIGURATION, iid2);
+        final YangInstanceIdentifier.NodeIdentifier identifier = ((ContainerNode) ((SingletonSet) payload.getData().getValue()).iterator().next()).getIdentifier();
+        final YangInstanceIdentifier node = payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier);
+        doReturn(Futures.immediateCheckedFuture(false)).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, payload.getData());
+        doReturn(Futures.immediateCheckedFuture(null)).when(readWrite).submit();
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChain);
+        final Response response = PostDataTransactionUtil.postData(uriInfo, payload, wrapper, refSchemaCtx);
+        assertEquals(201, response.getStatus());
+        verify(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, (NormalizedNode<?, ?>) ((SingletonSet) payload.getData().getValue()).iterator().next());
+    }
+
+    @Test
+    public void testPostListData() {
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext = new InstanceIdentifierContext<>(iid2, null, null, schema);
+        final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, buildList);
+
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(read).read(LogicalDatastoreType.CONFIGURATION, iid2);
+        final MapNode data = (MapNode) payload.getData();
+        final YangInstanceIdentifier.NodeIdentifierWithPredicates identifier = data.getValue().iterator().next().getIdentifier();
+        final YangInstanceIdentifier node = payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier);
+        doReturn(Futures.immediateCheckedFuture(false)).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, payload.getData());
+        doReturn(Futures.immediateCheckedFuture(null)).when(readWrite).submit();
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChain);
+        final Response response = PostDataTransactionUtil.postData(uriInfo, payload, wrapper, refSchemaCtx);
+        assertEquals(201, response.getStatus());
+        verify(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, data.getValue().iterator().next());
+    }
+
+    @Test
+    public void testPostDataFail() {
+        final InstanceIdentifierContext<? extends SchemaNode> iidContext = new InstanceIdentifierContext<>(iid2, null, null, schema);
+        final NormalizedNodeContext payload = new NormalizedNodeContext(iidContext, buildBaseCont);
+
+        doReturn(Futures.immediateCheckedFuture(Optional.absent())).when(read).read(LogicalDatastoreType.CONFIGURATION, iid2);
+        final YangInstanceIdentifier.NodeIdentifier identifier = ((ContainerNode) ((SingletonSet) payload.getData().getValue()).iterator().next()).getIdentifier();
+        final YangInstanceIdentifier node = payload.getInstanceIdentifierContext().getInstanceIdentifier().node(identifier);
+        doReturn(Futures.immediateCheckedFuture(false)).when(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        doNothing().when(readWrite).put(LogicalDatastoreType.CONFIGURATION, node,
+                payload.getData());
+        doReturn(Futures.immediateFailedCheckedFuture(new DOMException((short) 414, "Post request failed"))).when(readWrite).submit();
+        final TransactionVarsWrapper wrapper = new TransactionVarsWrapper(payload.getInstanceIdentifierContext(), null, transactionChain);
+        final Response response = PostDataTransactionUtil.postData(uriInfo, payload, wrapper, refSchemaCtx);
+        assertEquals(Response.Status.INTERNAL_SERVER_ERROR, response.getStatusInfo());
+        verify(readWrite).exists(LogicalDatastoreType.CONFIGURATION, node);
+        verify(readWrite).put(LogicalDatastoreType.CONFIGURATION, node, (NormalizedNode<?, ?>) ((SingletonSet) payload.getData().getValue()).iterator().next());
+    }
+
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/jukebox/jukebox@2015-04-04.yang b/restconf/sal-rest-connector/src/test/resources/jukebox/jukebox@2015-04-04.yang
new file mode 100644 (file)
index 0000000..5c319e1
--- /dev/null
@@ -0,0 +1,243 @@
+module example-jukebox {
+
+      namespace "http://example.com/ns/example-jukebox";
+      prefix "jbox";
+
+      organization "Example, Inc.";
+      contact "support at example.com";
+      description "Example Jukebox Data Model Module";
+      revision "2015-04-04" {
+        description "Initial version.";
+        reference "example.com document 1-4673";
+      }
+
+      identity genre {
+        description "Base for all genre types";
+      }
+
+      // abbreviated list of genre classifications
+      identity alternative {
+        base genre;
+        description "Alternative music";
+      }
+      identity blues {
+        base genre;
+        description "Blues music";
+      }
+      identity country {
+        base genre;
+        description "Country music";
+      }
+      identity jazz {
+        base genre;
+        description "Jazz music";
+      }
+      identity pop {
+        base genre;
+        description "Pop music";
+      }
+      identity rock {
+        base genre;
+        description "Rock music";
+      }
+
+      container jukebox {
+        presence
+          "An empty container indicates that the jukebox
+           service is available";
+
+        description
+          "Represents a jukebox resource, with a library, playlists,
+           and a play operation.";
+
+        container library {
+
+          description "Represents the jukebox library resource.";
+
+          list artist {
+            key name;
+
+            description
+              "Represents one artist resource within the
+               jukebox library resource.";
+
+            leaf name {
+              type string {
+                length "1 .. max";
+              }
+              description "The name of the artist.";
+            }
+
+            list album {
+              key name;
+
+              description
+                "Represents one album resource within one
+                 artist resource, within the jukebox library.";
+
+              leaf name {
+                type string {
+                  length "1 .. max";
+                }
+                description "The name of the album.";
+              }
+
+              leaf genre {
+                type identityref { base genre; }
+                description
+                  "The genre identifying the type of music on
+                   the album.";
+              }
+
+              leaf year {
+                type uint16 {
+                  range "1900 .. max";
+                }
+                description "The year the album was released";
+              }
+
+              container admin {
+                description
+                  "Administrative information for the album.";
+
+                leaf label {
+                  type string;
+                  description "The label that released the album.";
+                }
+                leaf catalogue-number {
+                  type string;
+                  description "The album's catalogue number.";
+                }
+              }
+
+              list song {
+                key name;
+
+                description
+                  "Represents one song resource within one
+                   album resource, within the jukebox library.";
+
+                leaf name {
+                  type string {
+                     length "1 .. max";
+                  }
+                  description "The name of the song";
+                }
+                leaf location {
+                  type string;
+                  mandatory true;
+                  description
+                   "The file location string of the
+                    media file for the song";
+                }
+                leaf format {
+                  type string;
+                  description
+                    "An identifier string for the media type
+                     for the file associated with the
+                     'location' leaf for this entry.";
+                }
+                leaf length {
+                  type uint32;
+                  units "seconds";
+                  description
+                    "The duration of this song in seconds.";
+                }
+              }   // end list 'song'
+            }   // end list 'album'
+          }  // end list 'artist'
+
+          leaf artist-count {
+             type uint32;
+             units "songs";
+             config false;
+             description "Number of artists in the library";
+          }
+          leaf album-count {
+             type uint32;
+             units "albums";
+             config false;
+             description "Number of albums in the library";
+          }
+          leaf song-count {
+             type uint32;
+             units "songs";
+             config false;
+             description "Number of songs in the library";
+          }
+        }  // end library
+
+        list playlist {
+          key name;
+
+          description
+            "Example configuration data resource";
+
+          leaf name {
+            type string;
+            description
+              "The name of the playlist.";
+          }
+          leaf description {
+            type string;
+            description
+              "A comment describing the playlist.";
+          }
+
+          list song {
+            key index;
+            ordered-by user;
+
+            description
+              "Example nested configuration data resource";
+
+            leaf index {    // not really needed
+              type uint32;
+              description
+                "An arbitrary integer index for this playlist song.";
+            }
+            leaf id {
+              type leafref {
+                path "/jbox:jukebox/jbox:library/jbox:artist/" +
+                     "jbox:album/jbox:song/jbox:name";
+              }
+              mandatory true;
+              description
+                "Song identifier. Must identify an instance of
+                 /jukebox/library/artist/album/song/name.";
+            }
+          }
+        }
+
+        container player {
+          description
+            "Represents the jukebox player resource.";
+
+          leaf gap {
+            type decimal64 {
+              fraction-digits 1;
+              range "0.0 .. 2.0";
+            }
+            units "tenths of seconds";
+            description "Time gap between each song";
+          }
+        }
+      }
+
+      rpc play {
+        description "Control function for the jukebox player";
+        input {
+          leaf playlist {
+            type string;
+            mandatory true;
+            description "playlist name";
+          }
+
+          leaf song-number {
+            type uint32;
+            mandatory true;
+            description "Song number in playlist to play";
+          }
+        }
+      }
+   }