From 362ab0db04c7a8431ee771fbf3df8e879a81296c Mon Sep 17 00:00:00 2001 From: Ivan Hrasko Date: Wed, 19 Oct 2016 14:36:58 +0200 Subject: [PATCH] Unit test for PostDataTransactionUtil class Change-Id: I38e1d29daa1598609e1a706b16d1bb2f24012f0f Signed-off-by: miroslav.kovac Signed-off-by: Ivan Hrasko --- .../restful/utils/FutureCallbackTx.java | 1 + .../restful/utils/FutureDataFactory.java | 9 + .../utils/PostDataTransactionUtil.java | 15 +- .../restful/utils/ResponseFactory.java | 5 +- .../utils/PostDataTransactionUtilTest.java | 180 +++++++++++++ .../resources/jukebox/jukebox@2015-04-04.yang | 243 ++++++++++++++++++ 6 files changed, 449 insertions(+), 4 deletions(-) create mode 100644 restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtilTest.java create mode 100644 restconf/sal-rest-connector/src/test/resources/jukebox/jukebox@2015-04-04.yang diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureCallbackTx.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureCallbackTx.java index 01760376b9..e7829c3f89 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureCallbackTx.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureCallbackTx.java @@ -90,6 +90,7 @@ final class FutureCallbackTx { protected static void handlingLoggerAndValues(@Nullable final Throwable t, final String txType, final T result, final FutureDataFactory dataFactory) { if (t != null) { + dataFactory.setFailureStatus(); LOG.warn("Transaction({}) FAILED!", txType, t); if (t instanceof DOMRpcException) { final List rpcErrorList = new ArrayList<>(); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureDataFactory.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureDataFactory.java index bec46374d3..c6373bc6b6 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureDataFactory.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/FutureDataFactory.java @@ -10,9 +10,18 @@ package org.opendaylight.restconf.restful.utils; class FutureDataFactory { protected T result; + private boolean statusFail = false; void setResult(final T result) { this.result = result; } + void setFailureStatus() { + this.statusFail = true; + } + + boolean getFailureStatus() { + return statusFail; + } + } diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtil.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtil.java index f507837630..fc9c676c4e 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtil.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/PostDataTransactionUtil.java @@ -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 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(); diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ResponseFactory.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ResponseFactory.java index 5d8c4f56aa..b4137ac989 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ResponseFactory.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/restful/utils/ResponseFactory.java @@ -16,7 +16,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; final class ResponseFactory extends FutureDataFactory implements Builder { - 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 implements Builder content = Builders.leafBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(QName.create(baseQName, "name"))) + .withValue("name of band") + .build(); + final LeafNode 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 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 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 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 index 0000000000..5c319e10f5 --- /dev/null +++ b/restconf/sal-rest-connector/src/test/resources/jukebox/jukebox@2015-04-04.yang @@ -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"; + } + } + } + } -- 2.36.6