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.Arrays;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Optional;
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;
74 public class InvokeRpcMethodTest {
76 private static UriInfo uriInfo;
77 private static EffectiveModelContext schemaContext;
79 private final RestconfImpl restconfImpl;
80 private final ControllerContext controllerContext;
81 private final BrokerFacade brokerFacade = mock(BrokerFacade.class);
83 public InvokeRpcMethodTest() {
84 controllerContext = TestRestconfUtils.newControllerContext(schemaContext);
85 restconfImpl = RestconfImpl.newInstance(brokerFacade, controllerContext);
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);
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));
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).
108 public void invokeRpcMethodTest() {
109 controllerContext.findModuleNameByNamespace(XMLNamespace.of("invoke:rpc:module"));
111 final NormalizedNodeContext payload = prepareDomPayload();
113 final NormalizedNodeContext rpcResponse =
114 restconfImpl.invokeRpc("invoke-rpc-module:rpc-test", payload, uriInfo);
115 assertNotNull(rpcResponse);
116 assertNull(rpcResponse.getData());
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())) {
132 assertNotNull(rpcSchemaNode);
133 final InputSchemaNode rpcInputSchemaNode = rpcSchemaNode.getInput();
134 final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> container =
135 SchemaAwareBuilders.containerBuilder(rpcInputSchemaNode);
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);
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());
151 return new NormalizedNodeContext(InstanceIdentifierContext.ofRpcInput(schema, rpcSchemaNode, null),
156 public void testInvokeRpcWithNoPayloadRpc_FailNoErrors() {
157 final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
159 doReturn(immediateFailedFluentFuture(new DOMRpcImplementationNotAvailableException("testExeption")))
160 .when(brokerFacade).invokeRpc(eq(qname), any());
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());
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) {
172 final List<RestconfError> errors = restDocumentedException.getErrors();
173 assertTrue("RestconfError not found at index " + index, errors.size() > index);
175 RestconfError actual = errors.get(index);
177 assertEquals("getErrorType", expErrorType, actual.getErrorType());
178 assertEquals("getErrorTag", expErrorTag, actual.getErrorTag());
179 assertNotNull("getErrorMessage is null", actual.getErrorMessage());
181 if (expErrorMsg.isPresent()) {
182 assertEquals("getErrorMessage", expErrorMsg.get(), actual.getErrorMessage());
185 if (expAppTag.isPresent()) {
186 assertEquals("getErrorAppTag", expAppTag.get(), actual.getErrorAppTag());
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));
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());
201 final RestconfDocumentedException ex = assertThrows(RestconfDocumentedException.class,
202 () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
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));
210 verifyRestconfDocumentedException(ex, 1, ErrorType.RPC, ErrorTag.IN_USE, Optional.of("bar"),
211 Optional.of("app-tag"));
215 public void testInvokeRpcWithNoPayload_Success() {
216 final NormalizedNode resultObj = null;
217 final DOMRpcResult expResult = new DefaultDOMRpcResult(resultObj);
219 final QName qname = QName.create("(http://netconfcentral.org/ns/toaster?revision=2009-11-20)cancel-toast");
221 doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(qname), any());
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.
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);
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());
240 WebApplicationException exceptionToBeThrown = assertThrows(WebApplicationException.class,
241 () -> restconfImpl.invokeRpc("toaster:cancel-toast", null, uriInfo));
242 assertEquals(Response.Status.NO_CONTENT.getStatusCode(), exceptionToBeThrown.getResponse().getStatus());
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());
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");
259 final Module rpcModule = schemaContext.findModules("toaster").iterator().next();
260 assertNotNull(rpcModule);
261 final QName rpcQName = QName.create(rpcModule.getQNameModule(), "make-toast");
263 RpcDefinition rpcDef = null;
264 for (final RpcDefinition rpc : rpcModule.getRpcs()) {
265 if (rpcQName.isEqualWithoutRevision(rpc.getQName())) {
271 assertNotNull(rpcDef);
273 final NormalizedNodeContext payload = new NormalizedNodeContext(
274 InstanceIdentifierContext.ofLocalRpcInput(schemaContext, rpcDef),
275 SchemaAwareBuilders.containerBuilder(rpcDef.getInput()).build());
277 doReturn(immediateFluentFuture(expResult)).when(brokerFacade).invokeRpc(eq(path), any(NormalizedNode.class));
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.
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());
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");
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();
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();
324 final DOMRpcResult result = new DefaultDOMRpcResult(container);
326 doReturn(immediateFluentFuture(result)).when(brokerFacade).invokeRpc(eq(rpcDef.getQName()), any());
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());
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
343 public void testMountedRpcCallNoPayload_Success() throws Exception {
344 // FIXME find how to use mockito for it