Fix RPC/action output writeout 87/110487/5
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Mar 2024 09:40:42 +0000 (10:40 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 6 Mar 2024 10:40:30 +0000 (11:40 +0100)
AbstractNormalizedNodeBodyWriter's dispatch logic falls through to
writeData(), which ends up wrecking the output.

Add missing return statements and improve tests to cover these.

JIRA: NETCONF-1266
Change-Id: I5ab83bf7b6b79d54dedc76ce4785f120dfe61304
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/AbstractNormalizedNodeBodyWriter.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/AbstractRestconfTest.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/Netconf799Test.java
restconf/restconf-nb/src/test/java/org/opendaylight/restconf/nb/jaxrs/RestconfOperationsPostTest.java
restconf/restconf-nb/src/test/resources/instanceidentifier/yang/instance-identifier-module.yang

index f54d39b5f9f4ca02542f75a04fb1c3c2e4be1517..c4fbef3cef26b3d05f45c61743a9b8576f49b1dd 100644 (file)
@@ -45,9 +45,11 @@ abstract class AbstractNormalizedNodeBodyWriter implements MessageBodyWriter<Nor
             if (stmt instanceof RpcEffectiveStatement rpc) {
                 stack.enterSchemaTree(rpc.output().argument());
                 writeOperationOutput(stack, context.writerParameters(), (ContainerNode) context.data(), output);
+                return;
             } else if (stmt instanceof ActionEffectiveStatement action) {
                 stack.enterSchemaTree(action.output().argument());
                 writeOperationOutput(stack, context.writerParameters(), (ContainerNode) context.data(), output);
+                return;
             }
         }
         writeData(stack, context.writerParameters(), context.data(), output);
index 8323c2780f149f0ae330ba13c4361e0301667a21..48842c66a6e04a94ae26bb7cca5b9788f4e0da85 100644 (file)
@@ -15,12 +15,17 @@ import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.text.ParseException;
 import java.util.List;
 import java.util.function.Consumer;
 import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.MessageBodyWriter;
 import org.eclipse.jdt.annotation.NonNull;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -34,9 +39,12 @@ import org.opendaylight.mdsal.dom.api.DOMMountPointService;
 import org.opendaylight.mdsal.dom.api.DOMRpcService;
 import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
 import org.opendaylight.restconf.api.ApiPath;
+import org.opendaylight.restconf.api.MediaTypes;
 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
 import org.opendaylight.restconf.common.errors.RestconfError;
 import org.opendaylight.restconf.nb.rfc8040.AbstractJukeboxTest;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.JsonNormalizedNodeBodyWriter;
+import org.opendaylight.restconf.nb.rfc8040.jersey.providers.XmlNormalizedNodeBodyWriter;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
@@ -71,8 +79,33 @@ abstract class AbstractRestconfTest extends AbstractJukeboxTest {
         return JUKEBOX_SCHEMA;
     }
 
+    static final void assertJson(final String expectedJson, final NormalizedNodePayload payload) {
+        assertPayload(expectedJson, payload, new JsonNormalizedNodeBodyWriter(),
+            MediaTypes.APPLICATION_YANG_DATA_JSON);
+    }
+
+    static final void assertXml(final String expectedXml, final NormalizedNodePayload payload) {
+        assertPayload(expectedXml, payload, new XmlNormalizedNodeBodyWriter(), MediaTypes.APPLICATION_YANG_DATA_XML);
+    }
+
+    private static void assertPayload(final String expected, final NormalizedNodePayload payload,
+            final MessageBodyWriter<NormalizedNodePayload> writer, final String mediaType) {
+        final var baos = new ByteArrayOutputStream();
+        try {
+            writer.writeTo(payload, null, null, null, MediaType.valueOf(mediaType), null, baos);
+        } catch (IOException e) {
+            throw new AssertionError(e);
+        }
+        assertEquals(expected, baos.toString(StandardCharsets.UTF_8));
+    }
+
     static final NormalizedNode assertNormalizedNode(final int status, final Consumer<AsyncResponse> invocation) {
-        return assertEntity(NormalizedNodePayload.class, status, invocation).data();
+        return assertNormalizedNodePayload(status, invocation).data();
+    }
+
+    static final NormalizedNodePayload assertNormalizedNodePayload(final int status,
+            final Consumer<AsyncResponse> invocation) {
+        return assertEntity(NormalizedNodePayload.class, status, invocation);
     }
 
     static final <T> T assertEntity(final Class<T> expectedType, final int expectedStatus,
index 4f848ef2be64aee724e3a4f83f126cfb6ce04f64..c2b5155f8a801197a9dc550733130cd2e7505bb4 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.restconf.nb.jaxrs;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -32,6 +33,7 @@ import org.opendaylight.mdsal.dom.spi.FixedDOMSchemaService;
 import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult;
 import org.opendaylight.restconf.api.ApiPath;
 import org.opendaylight.restconf.nb.rfc8040.AbstractInstanceIdentifierTest;
+import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
 import org.opendaylight.restconf.server.mdsal.MdsalRestconfServer;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
@@ -41,6 +43,7 @@ import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absol
 @ExtendWith(MockitoExtension.class)
 class Netconf799Test extends AbstractInstanceIdentifierTest {
     private static final QName OUTPUT_QNAME = QName.create(CONT_QNAME, "output");
+    private static final Absolute RESET_PATH = Absolute.of(CONT_QNAME, CONT1_QNAME, RESET_QNAME);
 
     @Mock
     private UriInfo uriInfo;
@@ -59,9 +62,10 @@ class Netconf799Test extends AbstractInstanceIdentifierTest {
 
     @Test
     void testInvokeAction() throws Exception {
-        doReturn(Futures.immediateFuture(new SimpleDOMActionResult(
-            ImmutableNodes.newContainerBuilder().withNodeIdentifier(NodeIdentifier.create(OUTPUT_QNAME)).build())))
-            .when(actionService).invokeAction(eq(Absolute.of(CONT_QNAME, CONT1_QNAME, RESET_QNAME)), any(), any());
+        doReturn(Futures.immediateFuture(new SimpleDOMActionResult(ImmutableNodes.newContainerBuilder()
+            .withNodeIdentifier(NodeIdentifier.create(OUTPUT_QNAME))
+            .build())))
+            .when(actionService).invokeAction(eq(RESET_PATH), any(), any());
 
         final var restconf = new JaxRsRestconf(new MdsalRestconfServer(new FixedDOMSchemaService(IID_SCHEMA),
             dataBroker, rpcService, actionService, mountPointService));
@@ -78,4 +82,33 @@ class Netconf799Test extends AbstractInstanceIdentifierTest {
         assertEquals(204, response.getStatus());
         assertNull(response.getEntity());
     }
+
+    @Test
+    void testInvokeActionOutput() throws Exception {
+        doReturn(Futures.immediateFuture(new SimpleDOMActionResult(ImmutableNodes.newContainerBuilder()
+            .withNodeIdentifier(NodeIdentifier.create(OUTPUT_QNAME))
+            .withChild(ImmutableNodes.leafNode(QName.create(OUTPUT_QNAME, "timestamp"), "somevalue"))
+            .build())))
+            .when(actionService).invokeAction(eq(RESET_PATH), any(), any());
+
+        final var restconf = new JaxRsRestconf(new MdsalRestconfServer(new FixedDOMSchemaService(IID_SCHEMA),
+            dataBroker, rpcService, actionService, mountPointService));
+        doReturn(new MultivaluedHashMap<>()).when(uriInfo).getQueryParameters();
+        doReturn(true).when(asyncResponse).resume(captor.capture());
+        restconf.postDataJSON(ApiPath.parse("instance-identifier-module:cont/cont1/reset"),
+            stringInputStream("""
+            {
+              "instance-identifier-module:input": {
+                "delay": 600
+              }
+            }"""), uriInfo, asyncResponse);
+        final var response = captor.getValue();
+        assertEquals(200, response.getStatus());
+
+        final var payload = assertInstanceOf(NormalizedNodePayload.class, response.getEntity());
+        AbstractRestconfTest.assertJson("""
+            {"instance-identifier-module:output":{"timestamp":"somevalue"}}""", payload);
+        AbstractRestconfTest.assertXml("""
+            <output xmlns="instance:identifier:module"><timestamp>somevalue</timestamp></output>""", payload);
+    }
 }
index 8ea7064aa7eae183b406f0e3c61609e1cb295697..f95542e0740cf89fd433d40bbf32832f370ff68a 100644 (file)
@@ -55,7 +55,10 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         .build();
     private static final ContainerNode OUTPUT = ImmutableNodes.newContainerBuilder()
         .withNodeIdentifier(new NodeIdentifier(QName.create(RPC, "output")))
-        .withChild(ImmutableNodes.leafNode(QName.create(RPC, "content"), "operation result"))
+        .withChild(ImmutableNodes.newContainerBuilder()
+            .withNodeIdentifier(new NodeIdentifier(QName.create(RPC, "cont-out")))
+            .withChild(ImmutableNodes.leafNode(QName.create(RPC, "lf-out"), "operation result"))
+            .build())
         .build();
     private static final EffectiveModelContext MODEL_CONTEXT =
         YangParserTestUtils.parseYangResourceDirectory("/invoke-rpc");
@@ -185,7 +188,7 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
         doReturn(Futures.immediateFuture(new DefaultDOMRpcResult(OUTPUT, List.of())))
             .when(rpcService).invokeRpc(RPC, INPUT);
 
-        assertEquals(OUTPUT, assertNormalizedNode(200, ar -> restconf.operationsJsonPOST(
+        final var payload = assertNormalizedNodePayload(200, ar -> restconf.operationsJsonPOST(
             apiPath("invoke-rpc-module:rpc-test"),
             stringInputStream("""
                 {
@@ -194,7 +197,13 @@ class RestconfOperationsPostTest extends AbstractRestconfTest {
                       "lf" : "test"
                     }
                   }
-                }"""), uriInfo, ar)));
+                }"""), uriInfo, ar));
+        assertEquals(OUTPUT, payload.data());
+        assertJson("""
+            {"invoke-rpc-module:output":{"cont-out":{"lf-out":"operation result"}}}""", payload);
+        assertXml("""
+            <output xmlns="invoke:rpc:module"><cont-out><lf-out>operation result</lf-out></cont-out></output>""",
+            payload);
     }
 
     private void prepNNC(final ContainerNode result) {
index 6779d00de1ebb1dea6bc7c72c0f5406addc070b8..613e230e7d12e28e44181e7057f1f7a4b2585108 100644 (file)
@@ -14,6 +14,11 @@ module instance-identifier-module {
             default 0;
           }
         }
+        output {
+          leaf timestamp {
+            type string;
+          }
+        }
       }
     }
   }