Specialize RemoteDevice to NetconfMessage
[netconf.git] / netconf / sal-netconf-connector / src / test / java / org / opendaylight / netconf / sal / connect / netconf / util / NetconfBaseOpsTest.java
1 /*
2  * Copyright (c) 2016 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.netconf.sal.connect.netconf.util;
9
10 import static org.junit.Assert.assertEquals;
11 import static org.junit.Assert.assertThrows;
12 import static org.junit.Assert.assertTrue;
13 import static org.mockito.ArgumentMatchers.any;
14 import static org.mockito.ArgumentMatchers.argThat;
15 import static org.mockito.ArgumentMatchers.eq;
16 import static org.mockito.Mockito.verify;
17 import static org.mockito.Mockito.when;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.net.InetSocketAddress;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Optional;
26 import java.util.concurrent.ExecutionException;
27 import org.custommonkey.xmlunit.Diff;
28 import org.custommonkey.xmlunit.XMLUnit;
29 import org.junit.Before;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 import org.mockito.ArgumentMatcher;
33 import org.mockito.Mock;
34 import org.mockito.junit.MockitoJUnitRunner;
35 import org.opendaylight.mdsal.dom.api.DOMRpcService;
36 import org.opendaylight.netconf.api.ModifyAction;
37 import org.opendaylight.netconf.api.NetconfMessage;
38 import org.opendaylight.netconf.api.xml.XmlUtil;
39 import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
40 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceCommunicator;
41 import org.opendaylight.netconf.sal.connect.netconf.AbstractTestModelTest;
42 import org.opendaylight.netconf.sal.connect.netconf.sal.NetconfDeviceRpc;
43 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.NetconfMessageTransformer;
44 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
45 import org.opendaylight.netconf.util.NetconfUtil;
46 import org.opendaylight.yangtools.rfc8528.data.util.EmptyMountPointContext;
47 import org.opendaylight.yangtools.yang.common.QName;
48 import org.opendaylight.yangtools.yang.common.QNameModule;
49 import org.opendaylight.yangtools.yang.common.Revision;
50 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
51 import org.opendaylight.yangtools.yang.common.XMLNamespace;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
54 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
55 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
57 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
58 import org.w3c.dom.Document;
59 import org.w3c.dom.Element;
60 import org.w3c.dom.NamedNodeMap;
61 import org.xml.sax.SAXException;
62
63 @RunWith(MockitoJUnitRunner.StrictStubs.class)
64 public class NetconfBaseOpsTest extends AbstractTestModelTest {
65     private static final QNameModule TEST_MODULE = QNameModule.create(
66             XMLNamespace.of("test:namespace"), Revision.of("2013-07-22"));
67
68     private static final QName CONTAINER_C_QNAME = QName.create(TEST_MODULE, "c");
69     private static final NodeIdentifier CONTAINER_C_NID = NodeIdentifier.create(CONTAINER_C_QNAME);
70     private static final QName LEAF_A_QNAME = QName.create(TEST_MODULE, "a");
71     private static final NodeIdentifier LEAF_A_NID = NodeIdentifier.create(LEAF_A_QNAME);
72     private static final QName LEAF_B_QNAME = QName.create(TEST_MODULE, "b");
73     private static final NodeIdentifier LEAF_B_NID = NodeIdentifier.create(LEAF_B_QNAME);
74     private static final QName CONTAINER_D_QNAME = QName.create(TEST_MODULE, "d");
75     private static final NodeIdentifier CONTAINER_D_NID = NodeIdentifier.create(CONTAINER_D_QNAME);
76     private static final QName LEAF_X_QNAME = QName.create(TEST_MODULE, "x");
77     private static final NodeIdentifier LEAF_X_NID = NodeIdentifier.create(LEAF_X_QNAME);
78
79     private static final QName CONTAINER_E_QNAME = QName.create(TEST_MODULE, "e");
80     private static final NodeIdentifier CONTAINER_E_NID = NodeIdentifier.create(CONTAINER_E_QNAME);
81     private static final QName LEAF_Z_QNAME = QName.create(TEST_MODULE, "z");
82     private static final NodeIdentifier LEAF_Z_NID = NodeIdentifier.create(LEAF_Z_QNAME);
83
84     static {
85         XMLUnit.setIgnoreWhitespace(true);
86         XMLUnit.setIgnoreComments(true);
87     }
88
89     @Mock
90     private RemoteDeviceCommunicator listener;
91     private NetconfRpcFutureCallback callback;
92     private NetconfBaseOps baseOps;
93
94     @Before
95     public void setUp() throws Exception {
96         final InputStream okStream = getClass().getResourceAsStream("/netconfMessages/rpc-reply_ok.xml");
97         final InputStream dataStream = getClass().getResourceAsStream("/netconfMessages/rpc-reply_get.xml");
98         final NetconfMessage ok = new NetconfMessage(XmlUtil.readXmlToDocument(okStream));
99         final NetconfMessage data = new NetconfMessage(XmlUtil.readXmlToDocument(dataStream));
100         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME)))
101                 .thenReturn(RpcResultBuilder.success(data).buildFuture());
102         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME)))
103                 .thenReturn(RpcResultBuilder.success(data).buildFuture());
104         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME)))
105                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
106         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME)))
107                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
108         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME)))
109                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
110         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME)))
111                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
112         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_LOCK_QNAME)))
113                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
114         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME)))
115                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
116         when(listener.sendRequest(any(), eq(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME)))
117                 .thenReturn(RpcResultBuilder.success(ok).buildFuture());
118         final MessageTransformer<NetconfMessage> transformer = new NetconfMessageTransformer(
119             new EmptyMountPointContext(SCHEMA_CONTEXT), true, BASE_SCHEMAS.getBaseSchema());
120         final DOMRpcService rpc = new NetconfDeviceRpc(SCHEMA_CONTEXT, listener, transformer);
121         final RemoteDeviceId id =
122                 new RemoteDeviceId("device-1", InetSocketAddress.createUnresolved("localhost", 17830));
123         callback = new NetconfRpcFutureCallback("prefix", id);
124         baseOps = new NetconfBaseOps(rpc, new EmptyMountPointContext(SCHEMA_CONTEXT));
125     }
126
127     @Test
128     public void testLock() throws Exception {
129         baseOps.lock(callback, NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME);
130         verifyMessageSent("lock", NetconfMessageTransformUtil.NETCONF_LOCK_QNAME);
131     }
132
133     @Test
134     public void testLockCandidate() throws Exception {
135         baseOps.lockCandidate(callback);
136         verifyMessageSent("lock", NetconfMessageTransformUtil.NETCONF_LOCK_QNAME);
137     }
138
139     @Test
140     public void testUnlock() throws Exception {
141         baseOps.unlock(callback, NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME);
142         verifyMessageSent("unlock", NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME);
143     }
144
145     @Test
146     public void testUnlockCandidate() throws Exception {
147         baseOps.unlockCandidate(callback);
148         verifyMessageSent("unlock", NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME);
149     }
150
151     @Test
152     public void testLockRunning() throws Exception {
153         baseOps.lockRunning(callback);
154         verifyMessageSent("lock-running", NetconfMessageTransformUtil.NETCONF_LOCK_QNAME);
155     }
156
157     @Test
158     public void testUnlockRunning() throws Exception {
159         baseOps.unlockRunning(callback);
160         verifyMessageSent("unlock-running", NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME);
161     }
162
163     @Test
164     public void testDiscardChanges() throws Exception {
165         baseOps.discardChanges(callback);
166         verifyMessageSent("discardChanges", NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME);
167     }
168
169     @Test
170     public void testCommit() throws Exception {
171         baseOps.commit(callback);
172         verifyMessageSent("commit", NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME);
173     }
174
175     @Test
176     public void testValidateCandidate() throws Exception {
177         baseOps.validateCandidate(callback);
178         verifyMessageSent("validate", NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME);
179     }
180
181     @Test
182     public void testValidateRunning() throws Exception {
183         baseOps.validateRunning(callback);
184         verifyMessageSent("validate-running", NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME);
185     }
186
187
188     @Test
189     public void testCopyConfig() throws Exception {
190         baseOps.copyConfig(callback, NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME,
191                 NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME);
192         verifyMessageSent("copy-config", NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME);
193     }
194
195     @Test
196     public void testCopyRunningToCandidate() throws Exception {
197         baseOps.copyRunningToCandidate(callback);
198         verifyMessageSent("copy-config", NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME);
199     }
200
201     @Test
202     public void testGetConfigRunningData() throws Exception {
203         final Optional<NormalizedNode> dataOpt =
204                 baseOps.getConfigRunningData(callback, Optional.of(YangInstanceIdentifier.empty())).get();
205         assertTrue(dataOpt.isPresent());
206         assertEquals(NetconfUtil.NETCONF_DATA_QNAME, dataOpt.get().getIdentifier().getNodeType());
207     }
208
209     @Test
210     public void testGetData() throws Exception {
211         final Optional<NormalizedNode> dataOpt =
212                 baseOps.getData(callback, Optional.of(YangInstanceIdentifier.empty())).get();
213         assertTrue(dataOpt.isPresent());
214         assertEquals(NetconfUtil.NETCONF_DATA_QNAME, dataOpt.get().getIdentifier().getNodeType());
215     }
216
217     @Test
218     public void testGetConfigRunning() throws Exception {
219         baseOps.getConfigRunning(callback, Optional.empty());
220         verifyMessageSent("getConfig", NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME);
221     }
222
223     @Test
224     public void testGetConfigCandidate() throws Exception {
225         baseOps.getConfigCandidate(callback, Optional.empty());
226         verifyMessageSent("getConfig_candidate", NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME);
227     }
228
229     @Test
230     public void testGetConfigCandidateWithFilter() throws Exception {
231         final YangInstanceIdentifier id = YangInstanceIdentifier.builder()
232                 .node(CONTAINER_C_QNAME)
233                 .build();
234         baseOps.getConfigCandidate(callback, Optional.of(id));
235         verifyMessageSent("getConfig_candidate-filter", NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME);
236     }
237
238     @Test
239     public void testGet() throws Exception {
240         baseOps.get(callback, Optional.empty());
241         verifyMessageSent("get", NetconfMessageTransformUtil.NETCONF_GET_QNAME);
242     }
243
244     @Test
245     public void testEditConfigCandidate() throws Exception {
246         final LeafNode<Object> leaf = Builders.leafBuilder()
247                 .withNodeIdentifier(LEAF_A_NID)
248                 .withValue("leaf-value")
249                 .build();
250         final YangInstanceIdentifier leafId = YangInstanceIdentifier.builder()
251                 .node(CONTAINER_C_QNAME)
252                 .node(LEAF_A_NID)
253                 .build();
254         final DataContainerChild structure = baseOps.createEditConfigStructure(Optional.of(leaf),
255                 Optional.of(ModifyAction.REPLACE), leafId);
256         baseOps.editConfigCandidate(callback, structure, true);
257         verifyMessageSent("edit-config-test-module", NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
258     }
259
260     @Test
261     public void testDeleteContainerNodeCandidate() throws Exception {
262         final YangInstanceIdentifier containerId = YangInstanceIdentifier.builder()
263                 .node(CONTAINER_C_QNAME)
264                 .build();
265         final DataContainerChild structure = baseOps.createEditConfigStructure(Optional.empty(),
266                 Optional.of(ModifyAction.DELETE), containerId);
267         baseOps.editConfigCandidate(callback, structure, true);
268         verifyMessageSent("edit-config-delete-container-node-candidate",
269                 NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
270     }
271
272     @Test
273     public void testDeleteLeafNodeCandidate() throws Exception {
274         final YangInstanceIdentifier leafId = YangInstanceIdentifier.builder()
275                 .node(CONTAINER_C_QNAME)
276                 .node(LEAF_A_NID)
277                 .build();
278         final DataContainerChild structure = baseOps.createEditConfigStructure(Optional.empty(),
279                 Optional.of(ModifyAction.DELETE), leafId);
280         baseOps.editConfigCandidate(callback, structure, true);
281         verifyMessageSent("edit-config-delete-leaf-node-candidate",
282                 NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
283     }
284
285     @Test
286     public void testEditConfigRunning() throws Exception {
287         final LeafNode<Object> leaf = Builders.leafBuilder()
288                 .withNodeIdentifier(LEAF_A_NID)
289                 .withValue("leaf-value")
290                 .build();
291         final YangInstanceIdentifier leafId = YangInstanceIdentifier.builder()
292                 .node(CONTAINER_C_NID)
293                 .node(LEAF_A_NID)
294                 .build();
295         final DataContainerChild structure = baseOps.createEditConfigStructure(Optional.of(leaf),
296                 Optional.of(ModifyAction.REPLACE), leafId);
297         baseOps.editConfigRunning(callback, structure, ModifyAction.MERGE, true);
298         verifyMessageSent("edit-config-test-module-running", NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME);
299     }
300
301     @Test
302     public void testGetWithFields() throws ExecutionException, InterruptedException {
303         final YangInstanceIdentifier path = YangInstanceIdentifier.create(CONTAINER_C_NID);
304         final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(LEAF_A_NID);
305         final YangInstanceIdentifier leafBField = YangInstanceIdentifier.create(LEAF_B_NID);
306
307         baseOps.getData(callback, Optional.of(path), List.of(leafAField, leafBField)).get();
308         verify(listener).sendRequest(msg("/netconfMessages/get-fields-request.xml"),
309                 eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME));
310     }
311
312     @Test
313     public void testGetConfigWithFields() throws ExecutionException, InterruptedException {
314         final YangInstanceIdentifier path = YangInstanceIdentifier.create(CONTAINER_C_NID);
315         final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(LEAF_A_NID);
316         final YangInstanceIdentifier leafBField = YangInstanceIdentifier.create(LEAF_B_NID);
317
318         baseOps.getConfigRunningData(callback, Optional.of(path), List.of(leafAField, leafBField)).get();
319         verify(listener).sendRequest(msg("/netconfMessages/get-config-fields-request.xml"),
320                 eq(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME));
321     }
322
323     @Test
324     public void testGetDataWithoutFields() {
325         assertThrows(ExecutionException.class, () -> baseOps.getData(callback,
326                 Optional.of(YangInstanceIdentifier.empty()), Collections.emptyList()).get());
327     }
328
329     @Test
330     public void getConfigRunningDataWithoutFields() {
331         assertThrows(ExecutionException.class, () -> baseOps.getConfigRunningData(callback,
332                 Optional.of(YangInstanceIdentifier.empty()), Collections.emptyList()).get());
333     }
334
335     @Test
336     public void testGetWithFieldsAndEmptyParentPath() throws ExecutionException, InterruptedException {
337         final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(CONTAINER_C_NID, LEAF_A_NID);
338         final YangInstanceIdentifier leafXField = YangInstanceIdentifier.create(
339                 CONTAINER_C_NID, CONTAINER_D_NID, LEAF_X_NID);
340         final YangInstanceIdentifier leafZField = YangInstanceIdentifier.create(CONTAINER_E_NID, LEAF_Z_NID);
341
342         baseOps.getData(callback, Optional.of(YangInstanceIdentifier.empty()),
343                 List.of(leafAField, leafXField, leafZField)).get();
344         verify(listener).sendRequest(msg("/netconfMessages/get-with-multiple-subtrees.xml"),
345                 eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME));
346     }
347
348     @Test
349     public void testGetConfigWithFieldsAndEmptyParentPath() throws ExecutionException, InterruptedException {
350         final YangInstanceIdentifier leafAField = YangInstanceIdentifier.create(CONTAINER_C_NID, LEAF_A_NID);
351         final YangInstanceIdentifier leafXField = YangInstanceIdentifier.create(
352                 CONTAINER_C_NID, CONTAINER_D_NID, LEAF_X_NID);
353         final YangInstanceIdentifier leafZField = YangInstanceIdentifier.create(CONTAINER_E_NID, LEAF_Z_NID);
354
355         baseOps.getConfigRunningData(callback, Optional.of(YangInstanceIdentifier.empty()),
356                 List.of(leafAField, leafXField, leafZField)).get();
357         verify(listener).sendRequest(msg("/netconfMessages/get-config-with-multiple-subtrees.xml"),
358                 eq(NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME));
359     }
360
361     @Test
362     public void testGetWithRootFieldsAndEmptyParentPath() throws ExecutionException, InterruptedException {
363         final YangInstanceIdentifier contCField = YangInstanceIdentifier.create(CONTAINER_C_NID);
364         final YangInstanceIdentifier contDField = YangInstanceIdentifier.create(CONTAINER_E_NID);
365
366         baseOps.getData(callback, Optional.of(YangInstanceIdentifier.empty()), List.of(contCField, contDField)).get();
367         verify(listener).sendRequest(msg("/netconfMessages/get-with-multiple-root-subtrees.xml"),
368                 eq(NetconfMessageTransformUtil.NETCONF_GET_QNAME));
369     }
370
371     private void verifyMessageSent(final String fileName, final QName name) {
372         final String path = "/netconfMessages/" + fileName + ".xml";
373         verify(listener).sendRequest(msg(path), eq(name));
374     }
375
376     private static NetconfMessage msg(final String name) {
377         final InputStream stream = NetconfBaseOpsTest.class.getResourceAsStream(name);
378         try {
379             return argThat(new NetconfMessageMatcher(XmlUtil.readXmlToDocument(stream)));
380         } catch (SAXException | IOException e) {
381             throw new IllegalStateException("Failed to read xml file " + name, e);
382         }
383     }
384
385     private static class NetconfMessageMatcher implements ArgumentMatcher<NetconfMessage> {
386
387         private final Document expected;
388
389         NetconfMessageMatcher(final Document expected) {
390             this.expected = removeAttrs(expected);
391         }
392
393         @Override
394         public boolean matches(final NetconfMessage message) {
395             final Document actualDoc = removeAttrs(message.getDocument());
396             actualDoc.normalizeDocument();
397             expected.normalizeDocument();
398             final Diff diff = XMLUnit.compareXML(expected, actualDoc);
399             return diff.similar();
400         }
401
402         private static Document removeAttrs(final Document input) {
403             final Document copy = XmlUtil.newDocument();
404             copy.appendChild(copy.importNode(input.getDocumentElement(), true));
405             final Element element = copy.getDocumentElement();
406             final List<String> attrNames = new ArrayList<>();
407             final NamedNodeMap attributes = element.getAttributes();
408             for (int i = 0; i < attributes.getLength(); i++) {
409                 final String nodeName = attributes.item(i).getNodeName();
410                 if ("xmlns".equals(nodeName)) {
411                     continue;
412                 }
413                 attrNames.add(nodeName);
414             }
415             attrNames.forEach(element::removeAttribute);
416             return copy;
417         }
418     }
419
420 }