2 * Copyright (c) 2013, 2015 Brocade Communication Systems, Inc., Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.sal.restconf.impl.test;
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;
24 import java.io.FileNotFoundException;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Optional;
29 import javax.ws.rs.WebApplicationException;
30 import javax.ws.rs.core.MultivaluedHashMap;
31 import javax.ws.rs.core.MultivaluedMap;
32 import javax.ws.rs.core.Response;
33 import javax.ws.rs.core.UriInfo;
34 import org.junit.BeforeClass;
35 import org.junit.Ignore;
36 import org.junit.Test;
37 import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
38 import org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException;
39 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
40 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
41 import org.opendaylight.netconf.sal.rest.impl.NormalizedNodeContext;
42 import org.opendaylight.netconf.sal.restconf.impl.BrokerFacade;
43 import org.opendaylight.netconf.sal.restconf.impl.ControllerContext;
44 import org.opendaylight.netconf.sal.restconf.impl.RestconfImpl;
45 import org.opendaylight.restconf.common.ErrorTags;
46 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
47 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
48 import org.opendaylight.restconf.common.errors.RestconfError;
49 import org.opendaylight.yangtools.yang.common.ErrorTag;
50 import org.opendaylight.yangtools.yang.common.ErrorType;
51 import org.opendaylight.yangtools.yang.common.QName;
52 import org.opendaylight.yangtools.yang.common.RpcError;
53 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
54 import org.opendaylight.yangtools.yang.common.XMLNamespace;
55 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
56 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
57 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
58 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
59 import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
60 import org.opendaylight.yangtools.yang.data.api.schema.builder.NormalizedNodeBuilder;
61 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaAwareBuilders;
62 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
63 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
64 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
65 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
66 import org.opendaylight.yangtools.yang.model.api.InputSchemaNode;
67 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
68 import org.opendaylight.yangtools.yang.model.api.Module;
69 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
70 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
71 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
73 public class InvokeRpcMethodTest {
75 private static UriInfo uriInfo;
76 private static EffectiveModelContext schemaContext;
78 private final RestconfImpl restconfImpl;
79 private final ControllerContext controllerContext;
80 private final BrokerFacade brokerFacade = mock(BrokerFacade.class);
82 public InvokeRpcMethodTest() {
83 controllerContext = TestRestconfUtils.newControllerContext(schemaContext);
84 restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext);
88 public static void init() throws FileNotFoundException, ReactorException {
89 schemaContext = TestUtils.loadSchemaContext("/full-versions/yangs", "/invoke-rpc");
90 final Collection<? extends Module> allModules = schemaContext.getModules();
91 assertNotNull(allModules);
92 final Module module = TestUtils.resolveModule("invoke-rpc-module", allModules);
93 assertNotNull(module);
95 uriInfo = mock(UriInfo.class);
96 final MultivaluedMap<String, String> map = new MultivaluedHashMap<>();
97 map.put("prettyPrint", List.of("true"));
98 doReturn(map).when(uriInfo).getQueryParameters(any(Boolean.class));
102 * Test method invokeRpc in RestconfImpl class tests if composite node as input parameter of method invokeRpc
103 * (second argument) is wrapped to parent composite node which has QName equals to QName of rpc (resolved from
104 * string - first argument).
107 public void invokeRpcMethodTest() {
108 controllerContext.findModuleNameByNamespace(XMLNamespace.of("invoke:rpc:module"));
110 final NormalizedNodeContext payload = prepareDomPayload();
112 final NormalizedNodeContext rpcResponse =
113 restconfImpl.invokeRpc("invoke-rpc-module:rpc-test", payload, uriInfo);
114 assertNotNull(rpcResponse);
115 assertNull(rpcResponse.getData());
119 private NormalizedNodeContext prepareDomPayload() {
120 final EffectiveModelContext schema = controllerContext.getGlobalSchema();
121 final Module rpcModule = schema.findModules("invoke-rpc-module").iterator().next();
122 assertNotNull(rpcModule);
123 final QName rpcQName = QName.create(rpcModule.getQNameModule(), "rpc-test");
124 RpcDefinition rpcSchemaNode = null;
125 for (final RpcDefinition rpc : rpcModule.getRpcs()) {
126 if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
131 assertNotNull(rpcSchemaNode);
132 final InputSchemaNode rpcInputSchemaNode = rpcSchemaNode.getInput();
133 final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> container =
134 SchemaAwareBuilders.containerBuilder(rpcInputSchemaNode);
136 final QName contQName = QName.create(rpcModule.getQNameModule(), "cont");
137 final DataSchemaNode contSchemaNode = rpcInputSchemaNode.getDataChildByName(contQName);
138 assertTrue(contSchemaNode instanceof ContainerSchemaNode);
139 final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> contNode =
140 SchemaAwareBuilders.containerBuilder((ContainerSchemaNode) contSchemaNode);
142 final QName lfQName = QName.create(rpcModule.getQNameModule(), "lf");
143 final DataSchemaNode lfSchemaNode = ((ContainerSchemaNode) contSchemaNode).getDataChildByName(lfQName);
144 assertTrue(lfSchemaNode instanceof LeafSchemaNode);
145 final LeafNode<Object> lfNode =
146 SchemaAwareBuilders.leafBuilder((LeafSchemaNode) lfSchemaNode).withValue("any value").build();
147 contNode.withChild(lfNode);
148 container.withChild(contNode.build());
150 return new NormalizedNodeContext(InstanceIdentifierContext.ofRpcInput(schema, rpcSchemaNode, null),
155 public void testInvokeRpcWithNoPayloadRpc_FailNoErrors() {
156 final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
158 doReturn(immediateFailedFluentFuture(new DOMRpcImplementationNotAvailableException("testExeption")))
159 .when(brokerFacade).invokeRpc(eq(qname), any());
161 final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
162 () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
163 verifyRestconfDocumentedException(ex, 0, ErrorType.APPLICATION, ErrorTag.OPERATION_NOT_SUPPORTED,
164 Optional.empty(), Optional.empty());
167 void verifyRestconfDocumentedException(final RestconfDocumentedException restDocumentedException, final int index,
168 final ErrorType expErrorType, final ErrorTag expErrorTag, final Optional<String> expErrorMsg,
169 final Optional<String> expAppTag) {
171 final List<RestconfError> errors = restDocumentedException.getErrors();
172 assertTrue("RestconfError not found at index " + index, errors.size() > index);
174 RestconfError actual = errors.get(index);
176 assertEquals("getErrorType", expErrorType, actual.getErrorType());
177 assertEquals("getErrorTag", expErrorTag, actual.getErrorTag());
178 assertNotNull("getErrorMessage is null", actual.getErrorMessage());
180 if (expErrorMsg.isPresent()) {
181 assertEquals("getErrorMessage", expErrorMsg.get(), actual.getErrorMessage());
184 if (expAppTag.isPresent()) {
185 assertEquals("getErrorAppTag", expAppTag.get(), actual.getErrorAppTag());
190 public void testInvokeRpcWithNoPayloadRpc_FailWithRpcError() {
191 final List<RpcError> rpcErrors = List.of(
192 RpcResultBuilder.newError(ErrorType.TRANSPORT, new ErrorTag("bogusTag"), "foo"),
193 RpcResultBuilder.newWarning(ErrorType.RPC, ErrorTag.IN_USE, "bar", "app-tag", null, null));
195 final DOMRpcResult result = new DefaultDOMRpcResult(rpcErrors);
196 final QName path = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
197 doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(path), any());
199 final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
200 () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
202 // We are performing pass-through here of error-tag, hence the tag remains as specified, but we want to make
203 // sure the HTTP status remains the same as
204 final ErrorTag bogus = new ErrorTag("bogusTag");
205 verifyRestconfDocumentedException(ex, 0, ErrorType.TRANSPORT, bogus, Optional.of("foo"), Optional.empty());
206 assertEquals(ErrorTags.statusOf(ErrorTag.OPERATION_FAILED), ErrorTags.statusOf(bogus));
208 verifyRestconfDocumentedException(ex, 1, ErrorType.RPC, ErrorTag.IN_USE, Optional.of("bar"),
209 Optional.of("app-tag"));
213 public void testInvokeRpcWithNoPayload_Success() {
214 final NormalizedNode resultObj = null;
215 final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj);
217 final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
219 doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any());
221 final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo);
222 assertNotNull(output);
223 assertEquals(null, output.getData());
224 // additional validation in the fact that the restconfImpl does not
225 // throw an exception.
229 public void testInvokeRpcWithEmptyOutput() {
230 final ContainerNode resultObj = mock(ContainerNode.class);
231 doReturn(Set.of()).when(resultObj).body();
232 doCallRealMethod().when(resultObj).isEmpty();
233 final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj);
235 final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
236 doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any());
238 WebApplicationException exceptionToBeThrown = assertThrows(WebApplicationException.class,
239 () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
240 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), exceptionToBeThrown.getResponse().getStatus());
244 public void testInvokeRpcMethodWithBadMethodName() {
245 final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
246 () -> restconfImpl.invokeRpc("toaster:bad-method", null, uriInfo));
247 verifyRestconfDocumentedException(ex, 0, ErrorType.RPC, ErrorTag.UNKNOWN_ELEMENT,
248 Optional.empty(), Optional.empty());
253 public void testInvokeRpcMethodWithInput() {
254 final DOMRpcResult expResult = mock(DOMRpcResult.class);
255 final QName path = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)make-toast");
257 final Module rpcModule = schemaContext.findModules("toaster").iterator().next();
258 assertNotNull(rpcModule);
259 final QName rpcQName = QName.create(rpcModule.getQNameModule(), "make-toast");
261 RpcDefinition rpcDef = null;
262 for (final RpcDefinition rpc : rpcModule.getRpcs()) {
263 if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
269 assertNotNull(rpcDef);
271 final NormalizedNodeContext payload = new NormalizedNodeContext(
272 InstanceIdentifierContext.ofLocalRpcInput(schemaContext, rpcDef),
273 SchemaAwareBuilders.containerBuilder(rpcDef.getInput()).build());
275 doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(path), any(NormalizedNode.class));
277 final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:make-toast", payload, uriInfo);
278 assertNotNull(output);
279 assertEquals(null, output.getData());
280 // additional validation in the fact that the restconfImpl does not
281 // throw an exception.
285 public void testThrowExceptionWhenSlashInModuleName() {
286 final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
287 () -> restconfImpl.invokeRpc("toaster/slash", null, uriInfo));
288 verifyRestconfDocumentedException(ex, 0, ErrorType.PROTOCOL, ErrorTag.INVALID_VALUE,
289 Optional.empty(), Optional.empty());
293 public void testInvokeRpcWithNoPayloadWithOutput_Success() {
294 final SchemaContext schema = controllerContext.getGlobalSchema();
295 final Module rpcModule = schema.findModules("toaster").iterator().next();
296 assertNotNull(rpcModule);
297 final QName rpcQName = QName.create(rpcModule.getQNameModule(), "testOutput");
298 final QName rpcOutputQName = QName.create(rpcModule.getQNameModule(),"output");
300 RpcDefinition rpcDef = null;
301 ContainerLike rpcOutputSchemaNode = null;
302 for (final RpcDefinition rpc : rpcModule.getRpcs()) {
303 if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
304 rpcOutputSchemaNode = rpc.getOutput();
309 assertNotNull(rpcDef);
310 assertNotNull(rpcOutputSchemaNode);
311 final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> containerBuilder =
312 SchemaAwareBuilders.containerBuilder(rpcOutputSchemaNode);
313 final DataSchemaNode leafSchema = rpcOutputSchemaNode
314 .getDataChildByName(QName.create(rpcModule.getQNameModule(), "textOut"));
315 assertTrue(leafSchema instanceof LeafSchemaNode);
316 final NormalizedNodeBuilder<NodeIdentifier, Object, LeafNode<Object>> leafBuilder =
317 SchemaAwareBuilders.leafBuilder((LeafSchemaNode) leafSchema);
318 leafBuilder.withValue("brm");
319 containerBuilder.withChild(leafBuilder.build());
320 final ContainerNode container = containerBuilder.build();
322 final DOMRpcResult result = new DefaultDOMRpcResult(container);
324 doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(rpcDef.getQName()), any());
326 final NormalizedNodeContext output = restconfImpl.invokeRpc("toaster:testOutput", null, uriInfo);
327 assertNotNull(output);
328 assertNotNull(output.getData());
329 assertSame(container, output.getData());
330 assertNotNull(output.getInstanceIdentifierContext());
331 assertNotNull(output.getInstanceIdentifierContext().getSchemaContext());
335 * Tests calling of RestConfImpl method invokeRpc. In the method there is searched rpc in remote schema context.
336 * This rpc is then executed.
337 * I wasn't able to simulate calling of rpc on remote device therefore this testing method raise method when rpc is
341 public void testMountedRpcCallNoPayload_Success() throws Exception {
342 // FIXME find how to use mockito for it