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