Support leaf-nodes-only in RFC8040 northbound
[netconf.git] / restconf / restconf-nb-rfc8040 / src / test / java / org / opendaylight / restconf / nb / rfc8040 / streams / listeners / ListenerAdapterTest.java
1 /*
2  * Copyright (c) 2017 Red Hat, 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.restconf.nb.rfc8040.streams.listeners;
9
10 import static org.junit.Assert.assertTrue;
11 import static org.junit.Assert.fail;
12 import static org.mockito.Mockito.mock;
13
14 import com.google.common.util.concurrent.Uninterruptibles;
15 import java.io.IOException;
16 import java.net.URISyntaxException;
17 import java.net.URL;
18 import java.nio.charset.StandardCharsets;
19 import java.nio.file.Files;
20 import java.nio.file.Paths;
21 import java.util.concurrent.CountDownLatch;
22 import java.util.concurrent.TimeUnit;
23 import org.json.JSONException;
24 import org.json.JSONObject;
25 import org.junit.AfterClass;
26 import org.junit.Before;
27 import org.junit.BeforeClass;
28 import org.junit.Test;
29 import org.opendaylight.mdsal.binding.api.DataBroker;
30 import org.opendaylight.mdsal.binding.api.WriteTransaction;
31 import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
32 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
33 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
35 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
36 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
37 import org.opendaylight.restconf.nb.rfc8040.LeafNodesOnlyParam;
38 import org.opendaylight.restconf.nb.rfc8040.SkipNotificationDataParam;
39 import org.opendaylight.restconf.nb.rfc8040.StartTimeParam;
40 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
41 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
42 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
43 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder;
44 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key;
45 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping;
46 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
48 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
49 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
50 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
51 import org.skyscreamer.jsonassert.JSONAssert;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.xmlunit.assertj.XmlAssert;
55
56 public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
57     private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
58
59     private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json";
60     private static final String JSON_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.json";
61     private static final String JSON_NOTIF_LEAVES_DEL =  "/listener-adapter-test/notif-leaves-del.json";
62     private static final String JSON_NOTIF_CREATE = "/listener-adapter-test/notif-create.json";
63     private static final String JSON_NOTIF_UPDATE = "/listener-adapter-test/notif-update.json";
64     private static final String JSON_NOTIF_DEL = "/listener-adapter-test/notif-del.json";
65     private static final String JSON_NOTIF_WITHOUT_DATA_CREATE =
66             "/listener-adapter-test/notif-without-data-create.json";
67     private static final String JSON_NOTIF_WITHOUT_DATA_UPDATE =
68             "/listener-adapter-test/notif-without-data-update.json";
69     private static final String JSON_NOTIF_WITHOUT_DATA_DELETE =
70             "/listener-adapter-test/notif-without-data-del.json";
71
72     private static final String XML_NOTIF_CREATE = "/listener-adapter-test/notif-create.xml";
73     private static final String XML_NOTIF_UPDATE =  "/listener-adapter-test/notif-update.xml";
74     private static final String XML_NOTIF_DEL =  "/listener-adapter-test/notif-delete.xml";
75
76     private static final String XML_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.xml";
77     private static final String XML_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.xml";
78     private static final String XML_NOTIF_LEAVES_DEL =  "/listener-adapter-test/notif-leaves-delete.xml";
79
80     private static final String XML_NOTIF_WITHOUT_DATA_CREATE =
81             "/listener-adapter-test/notif-without-data-create.xml";
82     private static final String XML_NOTIF_WITHOUT_DATA_UPDATE =
83             "/listener-adapter-test/notif-without-data-update.xml";
84     private static final String XML_NOTIF_WITHOUT_DATA_DELETE =
85             "/listener-adapter-test/notif-without-data-delete.xml";
86
87     private static final YangInstanceIdentifier PATCH_CONT_YIID =
88             YangInstanceIdentifier.create(new NodeIdentifier(PatchCont.QNAME));
89
90     private static EffectiveModelContext SCHEMA_CONTEXT;
91
92     private DataBroker dataBroker;
93     private DOMDataBroker domDataBroker;
94     private SchemaContextHandler schemaContextHandler;
95
96     @BeforeClass
97     public static void beforeClass() {
98         SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource(
99                 "/instanceidentifier/yang/instance-identifier-patch-module.yang");
100     }
101
102     @AfterClass
103     public static void afterClass() {
104         SCHEMA_CONTEXT = null;
105     }
106
107     @Before
108     public void setUp() throws Exception {
109         dataBroker = getDataBroker();
110         domDataBroker = getDomBroker();
111
112         schemaContextHandler = new SchemaContextHandler(domDataBroker, mock(DOMSchemaService.class));
113         schemaContextHandler.onModelContextUpdated(SCHEMA_CONTEXT);
114     }
115
116     class ListenerAdapterTester extends ListenerAdapter {
117
118         private volatile String lastNotification;
119         private CountDownLatch notificationLatch = new CountDownLatch(1);
120
121         ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName,
122                               final NotificationOutputTypeGrouping.NotificationOutputType outputType,
123                               final boolean leafNodesOnly, final boolean skipNotificationData) {
124             super(path, streamName, outputType);
125             setQueryParams(StartTimeParam.forUriValue("1970-01-01T00:00:00Z"), null, null,
126                 LeafNodesOnlyParam.of(leafNodesOnly), SkipNotificationDataParam.of(skipNotificationData));
127         }
128
129         @Override
130         protected void post(final String data) {
131             lastNotification = data;
132             notificationLatch.countDown();
133         }
134
135         public void assertGot(final String json) throws JSONException {
136             // FIXME: use awaitility
137             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
138                 fail("Timed out waiting for notification for: " + json);
139             }
140
141             LOG.info("lastNotification: {}", lastNotification);
142             final String withFakeDate = withFakeDate(lastNotification);
143             LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
144
145             JSONAssert.assertEquals(json, withFakeDate, false);
146             lastNotification = null;
147             notificationLatch = new CountDownLatch(1);
148         }
149
150         public void assertXmlSimilar(final String xml) {
151             awaitUntillNotification(xml);
152
153             LOG.info("lastNotification: {}", lastNotification);
154             final String withFakeDate = withFakeXmlDate(lastNotification);
155             LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
156
157             XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
158             lastNotification = null;
159             notificationLatch = new CountDownLatch(1);
160         }
161
162         public String awaitUntillNotification(final String xml) {
163             // FIXME: use awaitility
164             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
165                 fail("Timed out waiting for notification for: " + xml);
166             }
167             return lastNotification;
168         }
169
170         public void resetLatch() {
171             notificationLatch = new CountDownLatch(1);
172         }
173     }
174
175     static String withFakeDate(final String in) throws JSONException {
176         final JSONObject doc = new JSONObject(in);
177         final JSONObject notification =
178                 doc.getJSONObject("urn-ietf-params-xml-ns-netconf-notification-1.0:notification");
179         if (notification == null) {
180             return in;
181         }
182         notification.put("event-time", "someDate");
183         return doc.toString();
184     }
185
186     static String withFakeXmlDate(final String in) {
187         return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
188     }
189
190     private String getNotifJson(final String path) throws IOException, URISyntaxException, JSONException {
191         final URL url = getClass().getResource(path);
192         final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
193         return withFakeDate(new String(bytes, StandardCharsets.UTF_8));
194     }
195
196     private String getResultXml(final String path) throws IOException, URISyntaxException, JSONException {
197         final URL url = getClass().getResource(path);
198         final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
199         return withFakeXmlDate(new String(bytes, StandardCharsets.UTF_8));
200     }
201
202     @Test
203     public void testJsonNotifsLeaves() throws Exception {
204         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
205                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, true, false);
206         adapter.setCloseVars(domDataBroker, schemaContextHandler);
207
208         final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
209                 .getInstance(DOMDataTreeChangeService.class);
210         final DOMDataTreeIdentifier root =
211                 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
212         changeService.registerDataTreeChangeListener(root, adapter);
213
214         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
215         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
216         final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
217                 .child(MyList1.class, new MyList1Key("Althea"));
218         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
219         writeTransaction.commit();
220         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
221
222         writeTransaction = dataBroker.newWriteOnlyTransaction();
223         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
224         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
225         writeTransaction.commit();
226         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
227
228         writeTransaction = dataBroker.newWriteOnlyTransaction();
229         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
230         writeTransaction.commit();
231         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
232     }
233
234     @Test
235     public void testJsonNotifs() throws Exception {
236         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
237                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, false);
238         adapter.setCloseVars(domDataBroker, schemaContextHandler);
239
240         final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
241                 .getInstance(DOMDataTreeChangeService.class);
242         final DOMDataTreeIdentifier root =
243                 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
244         changeService.registerDataTreeChangeListener(root, adapter);
245
246         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
247         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
248         final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
249                 .child(MyList1.class, new MyList1Key("Althea"));
250         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
251         writeTransaction.commit();
252         adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE));
253
254         writeTransaction = dataBroker.newWriteOnlyTransaction();
255         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
256         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
257         writeTransaction.commit();
258         adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE));
259
260         writeTransaction = dataBroker.newWriteOnlyTransaction();
261         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
262         writeTransaction.commit();
263         adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
264     }
265
266     @Test
267     public void testJsonNotifsWithoutData() throws Exception {
268         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
269                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, true);
270         adapter.setCloseVars(domDataBroker, schemaContextHandler);
271
272         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
273                 .getInstance(DOMDataTreeChangeService.class);
274         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
275         changeService.registerDataTreeChangeListener(root, adapter);
276         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
277         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
278         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
279                 .child(MyList1.class, new MyList1Key("Althea"));
280         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
281         writeTransaction.commit();
282         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_CREATE));
283
284         writeTransaction = dataBroker.newWriteOnlyTransaction();
285         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
286         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
287         writeTransaction.commit();
288         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_UPDATE));
289
290         writeTransaction = dataBroker.newWriteOnlyTransaction();
291         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
292         writeTransaction.commit();
293         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
294     }
295
296     @Test
297     public void testXmlNotifications() throws Exception {
298         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
299                 NotificationOutputTypeGrouping.NotificationOutputType.XML, false, false);
300         adapter.setCloseVars(domDataBroker, schemaContextHandler);
301
302         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
303                 .getInstance(DOMDataTreeChangeService.class);
304         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
305         changeService.registerDataTreeChangeListener(root, adapter);
306         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
307         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
308         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
309                 .child(MyList1.class, new MyList1Key("Althea"));
310         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
311         writeTransaction.commit();
312         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CREATE));
313
314         writeTransaction = dataBroker.newWriteOnlyTransaction();
315         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
316         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
317         writeTransaction.commit();
318         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_UPDATE));
319
320         writeTransaction = dataBroker.newWriteOnlyTransaction();
321         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
322         writeTransaction.commit();
323         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_DEL));
324     }
325
326     @Test
327     public void testXmlSkipData() throws Exception {
328         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
329                 NotificationOutputTypeGrouping.NotificationOutputType.XML, false, true);
330         adapter.setCloseVars(domDataBroker, schemaContextHandler);
331
332         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
333                 .getInstance(DOMDataTreeChangeService.class);
334         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
335         changeService.registerDataTreeChangeListener(root, adapter);
336         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
337         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
338         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
339                 .child(MyList1.class, new MyList1Key("Althea"));
340         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
341         writeTransaction.commit();
342         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_CREATE));
343
344         writeTransaction = dataBroker.newWriteOnlyTransaction();
345         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
346         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
347         writeTransaction.commit();
348         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_UPDATE));
349
350         writeTransaction = dataBroker.newWriteOnlyTransaction();
351         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
352         writeTransaction.commit();
353         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_DELETE));
354     }
355
356     @Test
357     public void testXmlLeavesOnly() throws Exception {
358         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
359                 NotificationOutputTypeGrouping.NotificationOutputType.XML, true, false);
360         adapter.setCloseVars(domDataBroker, schemaContextHandler);
361
362         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
363                 .getInstance(DOMDataTreeChangeService.class);
364         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
365         changeService.registerDataTreeChangeListener(root, adapter);
366         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
367         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
368         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
369                 .child(MyList1.class, new MyList1Key("Althea"));
370         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
371         writeTransaction.commit();
372         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_CREATE));
373
374         writeTransaction = dataBroker.newWriteOnlyTransaction();
375         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
376         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
377         writeTransaction.commit();
378         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_UPDATE));
379
380         writeTransaction = dataBroker.newWriteOnlyTransaction();
381         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
382         writeTransaction.commit();
383
384         // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
385         final String notification = adapter.awaitUntillNotification("");
386         assertTrue(notification.contains("instance-identifier-patch-module:my-leaf12"));
387         assertTrue(notification.contains("instance-identifier-patch-module:my-leaf11"));
388         assertTrue(notification.contains("instance-identifier-patch-module:name"));
389     }
390 }