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