Reconstruct inference stack during normalization
[netconf.git] / restconf / restconf-nb-bierman02 / src / test / java / org / opendaylight / controller / sal / restconf / impl / test / InvokeRpcMethodTest.java
1 /*
2  * Copyright (c) 2013, 2015 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.sal.restconf.impl.test;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertNotNull;
12 import static org.junit.Assert.assertNull;
13 import static org.junit.Assert.assertSame;
14 import static org.junit.Assert.assertThrows;
15 import static org.junit.Assert.assertTrue;
16 import static org.mockito.ArgumentMatchers.any;
17 import static org.mockito.ArgumentMatchers.eq;
18 import static org.mockito.Mockito.doCallRealMethod;
19 import static org.mockito.Mockito.doReturn;
20 import static org.mockito.Mockito.mock;
21 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture;
22 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFluentFuture;
23
24 import java.io.FileNotFoundException;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.Set;
30 import javax.ws.rs.WebApplicationException;
31 import javax.ws.rs.core.MultivaluedHashMap;
32 import javax.ws.rs.core.MultivaluedMap;
33 import javax.ws.rs.core.Response;
34 import javax.ws.rs.core.UriInfo;
35 import org.junit.BeforeClass;
36 import org.junit.Ignore;
37 import org.junit.Test;
38 import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
39 import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
40 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
41 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
42 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
43 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
44 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
45 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
46 import org.opendaylight.restconf.common.ErrorTags;
47 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
48 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
49 import org.opendaylight.restconf.common.errors.RestconfError;
50 import org.opendaylight.yangtools.yang.common.ErrorTag;
51 import org.opendaylight.yangtools.yang.common.ErrorType;
52 import org.opendaylight.yangtools.yang.common.QName;
53 import org.opendaylight.yangtools.yang.common.RpcError;
54 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
55 import org.opendaylight.yangtools.yang.common.XMLNamespace;
56 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
57 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
58 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
59 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
60 import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
61 import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeBuilder;
62 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
63 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
64 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
65 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
66 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
67 import org.opendaylight.yangtools.yang.model.api.InputSchemaNode;
68 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.Module;
70 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
71 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
72 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
73
74 public class InvokeRpcMethodTest {
75
76     private static UriInfo uriInfo;
77     private static EffectiveModelContext schemaContext;
78
79     private final RestconfImpl restconfImpl;
80     private final ControllerContext controllerContext;
81     private final BrokerFacade brokerFacade = mock(BrokerFacade.class);
82
83     public InvokeRpcMethodTest() {
84         controllerContext = TestRestconfUtils.newControllerContext(schemaContext);
85         restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext);
86     }
87
88     @BeforeClass
89     public static void init() throws FileNotFoundException, ReactorException {
90         schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs", "/invoke-rpc");
91         final Collection<? extends Module> allModules = schemaContext.getModules();
92         assertNotNull(allModules);
93         final Module module = TestUtils.resolveModule("invoke-rpc-module", allModules);
94         assertNotNull(module);
95
96         uriInfo = mock(UriInfo.class);
97         final MultivaluedMap<String, String> map = new MultivaluedHashMap<>();
98         map.put("prettyPrint", List.of("true"));
99         doReturn(map).when(uriInfo).getQueryParameters(any(Boolean.class));
100     }
101
102     /**
103      * Test method invokeRpc in RestconfImpl class tests if composite node as input parameter of method invokeRpc
104      * (second argument) is wrapped to parent composite node which has QName equals to QName of rpc (resolved from
105      * string - first argument).
106      */
107     @Test
108     public void invokeRpcMethodTest() {
109         controllerContext.findModuleNameByNamespace(XMLNamespace.of("invoke:rpc:module"));
110
111         final NormalizedNodeContext payload = prepareDomPayload();
112
113         final NormalizedNodeContext rpcResponse =
114                 restconfImpl.invokeRpc("invoke-rpc-module:rpc-test", payload, uriInfo);
115         assertNotNull(rpcResponse);
116         assertNull(rpcResponse.getData());
117
118     }
119
120     private NormalizedNodeContext prepareDomPayload() {
121         final EffectiveModelContext schema = controllerContext.getGlobalSchema();
122         final Module rpcModule = schema.findModules("invoke-rpc-module").iterator().next();
123         assertNotNull(rpcModule);
124         final QName rpcQName = QName.create(rpcModule.getQNameModule(), "rpc-test");
125         RpcDefinition rpcSchemaNode = null;
126         for (final RpcDefinition rpc : rpcModule.getRpcs()) {
127             if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
128                 rpcSchemaNode = rpc;
129                 break;
130             }
131         }
132         assertNotNull(rpcSchemaNode);
133         final InputSchemaNode rpcInputSchemaNode = rpcSchemaNode.getInput();
134         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> container =
135                 SchemaAwareBuilders.containerBuilder(rpcInputSchemaNode);
136
137         final QName contQName = QName.create(rpcModule.getQNameModule(), "cont");
138         final DataSchemaNode contSchemaNode = rpcInputSchemaNode.getDataChildByName(contQName);
139         assertTrue(contSchemaNode instanceof ContainerSchemaNode);
140         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contNode =
141                 SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) contSchemaNode);
142
143         final QName lfQName = QName.create(rpcModule.getQNameModule(), "lf");
144         final DataSchemaNode lfSchemaNode = ((ContainerSchemaNode) contSchemaNode).getDataChildByName(lfQName);
145         assertTrue(lfSchemaNode instanceof LeafSchemaNode);
146         final LeafNode<Object> lfNode =
147                 SchemaAwareBuilders.leafBuilder((LeafSchemaNode) lfSchemaNode).withValue("any value").build();
148         contNode.withChild(lfNode);
149         container.withChild(contNode.build());
150
151         return new NormalizedNodeContext(InstanceIdentifierContext.ofRpcInput(schema, rpcSchemaNode, null),
152             container.build());
153     }
154
155     @Test
156     public void testInvokeRpcWithNoPayloadRpc_FailNoErrors() {
157         final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
158
159         doReturn(immediateFailedFluentFuture(new DOMRpcImplementationNotAvailableException("testExeption")))
160             .when(brokerFacade).invokeRpc(eq(qname), any());
161
162         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
163             () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
164         verifyRestconfDocumentedException(ex, 0, ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED,
165             Optional.empty(), Optional.empty());
166     }
167
168     void verifyRestconfDocumentedException(final RestconfDocumentedException restDocumentedException, final int index,
169             final ErrorType expErrorType, final ErrorTag expErrorTag, final Optional<String> expErrorMsg,
170             final Optional<String> expAppTag) {
171
172         final List<RestconfError> errors = restDocumentedException.getErrors();
173         assertTrue("RestconfError not found at index " + index, errors.size() > index);
174
175         RestconfError actual = errors.get(index);
176
177         assertEquals("getErrorType", expErrorType, actual.getErrorType());
178         assertEquals("getErrorTag", expErrorTag, actual.getErrorTag());
179         assertNotNull("getErrorMessage is null", actual.getErrorMessage());
180
181         if (expErrorMsg.isPresent()) {
182             assertEquals("getErrorMessage", expErrorMsg.get(), actual.getErrorMessage());
183         }
184
185         if (expAppTag.isPresent()) {
186             assertEquals("getErrorAppTag", expAppTag.get(), actual.getErrorAppTag());
187         }
188     }
189
190     @Test
191     public void testInvokeRpcWithNoPayloadRpc_FailWithRpcError() {
192         final List<RpcError> rpcErrors = Arrays.asList(
193                 RpcResultBuilder.newError(RpcError.ErrorType.TRANSPORT, "bogusTag", "foo"),
194                 RpcResultBuilder.newWarning(RpcError.ErrorType.RPC, "in-use", "bar",
195                         "app-tag", null, null));
196
197         final DOMRpcResult result = new DefaultDOMRpcResult(rpcErrors);
198         final QName path = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
199         doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(path), any());
200
201         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
202             () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
203
204         // We are performing pass-through here of error-tag, hence the tag remains as specified, but we want to make
205         // sure the HTTP status remains the same as
206         final ErrorTag bogus = new ErrorTag("bogusTag");
207         verifyRestconfDocumentedException(ex, 0, ErrorType.TRANSPORT, bogus, Optional.of("foo"), Optional.empty());
208         assertEquals(ErrorTags.statusOf(ErrorTag.OPERATION_FAILED), ErrorTags.statusOf(bogus));
209
210         verifyRestconfDocumentedException(ex, 1, ErrorType.RPC, ErrorTag.IN_USE, Optional.of("bar"),
211             Optional.of("app-tag"));
212     }
213
214     @Test
215     public void testInvokeRpcWithNoPayload_Success() {
216         final NormalizedNode resultObj = null;
217         final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj);
218
219         final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
220
221         doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any());
222
223         final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo);
224         assertNotNull(output);
225         assertEquals(null, output.getData());
226         // additional validation in the fact that the restconfImpl does not
227         // throw an exception.
228     }
229
230     @Test
231     public void testInvokeRpcWithEmptyOutput() {
232         final ContainerNode resultObj = mock(ContainerNode.class);
233         doReturn(Set.of()).when(resultObj).body();
234         doCallRealMethod().when(resultObj).isEmpty();
235         final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj);
236
237         final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
238         doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any());
239
240         WebApplicationException exceptionToBeThrown = assertThrows(WebApplicationException.class,
241             () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
242         assertEquals(Response.Status.NO_CONTENT.getStatusCode(), exceptionToBeThrown.getResponse().getStatus());
243     }
244
245     @Test
246     public void testInvokeRpcMethodWithBadMethodName() {
247         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
248             () -> restconfImpl.invokeRpc("toaster:bad-method", null, uriInfo));
249         verifyRestconfDocumentedException(ex, 0, ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT,
250             Optional.empty(), Optional.empty());
251     }
252
253     @Test
254     @Ignore
255     public void testInvokeRpcMethodWithInput() {
256         final DOMRpcResult expResult = mock(DOMRpcResult.class);
257         final QName path = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)make-toast");
258
259         final Module rpcModule = schemaContext.findModules("toaster").iterator().next();
260         assertNotNull(rpcModule);
261         final QName rpcQName = QName.create(rpcModule.getQNameModule(), "make-toast");
262
263         RpcDefinition rpcDef = null;
264         for (final RpcDefinition rpc : rpcModule.getRpcs()) {
265             if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
266                 rpcDef = rpc;
267                 break;
268             }
269         }
270
271         assertNotNull(rpcDef);
272
273         final NormalizedNodeContext payload = new NormalizedNodeContext(
274                 InstanceIdentifierContext.ofLocalRpcInput(schemaContext, rpcDef),
275                 SchemaAwareBuilders.containerBuilder(rpcDef.getInput()).build());
276
277         doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(path), any(NormalizedNode.class));
278
279         final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:make-toast", payload, uriInfo);
280         assertNotNull(output);
281         assertEquals(null, output.getData());
282         // additional validation in the fact that the restconfImpl does not
283         // throw an exception.
284     }
285
286     @Test
287     public void testThrowExceptionWhenSlashInModuleName() {
288         final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
289             () -> restconfImpl.invokeRpc("toaster/slash", null, uriInfo));
290         verifyRestconfDocumentedException(ex, 0, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
291             Optional.empty(), Optional.empty());
292     }
293
294     @Test
295     public void testInvokeRpcWithNoPayloadWithOutput_Success() {
296         final SchemaContext schema = controllerContext.getGlobalSchema();
297         final Module rpcModule = schema.findModules("toaster").iterator().next();
298         assertNotNull(rpcModule);
299         final QName rpcQName = QName.create(rpcModule.getQNameModule(), "testOutput");
300         final QName rpcOutputQName = QName.create(rpcModule.getQNameModule(),"output");
301
302         RpcDefinition rpcDef = null;
303         ContainerLike rpcOutputSchemaNode = null;
304         for (final RpcDefinition rpc : rpcModule.getRpcs()) {
305             if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
306                 rpcOutputSchemaNode = rpc.getOutput();
307                 rpcDef = rpc;
308                 break;
309             }
310         }
311         assertNotNull(rpcDef);
312         assertNotNull(rpcOutputSchemaNode);
313         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> containerBuilder =
314                 SchemaAwareBuilders.containerBuilder(rpcOutputSchemaNode);
315         final DataSchemaNode leafSchema = rpcOutputSchemaNode
316                 .getDataChildByName(QName.create(rpcModule.getQNameModule(), "textOut"));
317         assertTrue(leafSchema instanceof LeafSchemaNode);
318         final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
319                 SchemaAwareBuilders.leafBuilder((LeafSchemaNode) leafSchema);
320         leafBuilder.withValue("brm");
321         containerBuilder.withChild(leafBuilder.build());
322         final ContainerNode container = containerBuilder.build();
323
324         final DOMRpcResult result = new DefaultDOMRpcResult(container);
325
326         doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(rpcDef.getQName()), any());
327
328         final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:testOutput", null, uriInfo);
329         assertNotNull(output);
330         assertNotNull(output.getData());
331         assertSame(container, output.getData());
332         assertNotNull(output.getInstanceIdentifierContext());
333         assertNotNull(output.getInstanceIdentifierContext().getSchemaContext());
334     }
335
336     /**
337      * Tests calling of RestConfImpl method invokeRpc. In the method there is searched rpc in remote schema context.
338      * This rpc is then executed.
339      * I wasn't able to simulate calling of rpc on remote device therefore this testing method raise method when rpc is
340      * invoked.
341      */
342     @Test
343     public void testMountedRpcCallNoPayload_Success() throws Exception {
344         // FIXME find how to use mockito for it
345     }
346 }