6d8999c3eaf6d3211fd2f66c8a5f6120eb99f3eb
[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 java.time.Instant.EPOCH;
11 import static org.junit.Assert.assertTrue;
12 import static org.junit.Assert.fail;
13 import static org.mockito.Mockito.mock;
14
15 import com.google.common.util.concurrent.Uninterruptibles;
16 import java.io.IOException;
17 import java.net.URISyntaxException;
18 import java.net.URL;
19 import java.nio.charset.StandardCharsets;
20 import java.nio.file.Files;
21 import java.nio.file.Paths;
22 import java.util.concurrent.CountDownLatch;
23 import java.util.concurrent.TimeUnit;
24 import org.json.JSONException;
25 import org.json.JSONObject;
26 import org.junit.AfterClass;
27 import org.junit.Before;
28 import org.junit.BeforeClass;
29 import org.junit.Test;
30 import org.opendaylight.mdsal.binding.api.DataBroker;
31 import org.opendaylight.mdsal.binding.api.WriteTransaction;
32 import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
33 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
35 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
36 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
37 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
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(EPOCH, null, null, leafNodesOnly, skipNotificationData);
124         }
125
126         @Override
127         protected void post(final String data) {
128             this.lastNotification = data;
129             notificationLatch.countDown();
130         }
131
132         public void assertGot(final String json) throws JSONException {
133             // FIXME: use awaitility
134             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
135                 fail("Timed out waiting for notification for: " + json);
136             }
137
138             LOG.info("lastNotification: {}", lastNotification);
139             final String withFakeDate = withFakeDate(lastNotification);
140             LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
141
142             JSONAssert.assertEquals(json, withFakeDate, false);
143             this.lastNotification = null;
144             notificationLatch = new CountDownLatch(1);
145         }
146
147         public void assertXmlSimilar(String xml) {
148             awaitUntillNotification(xml);
149
150             LOG.info("lastNotification: {}", lastNotification);
151             final String withFakeDate = withFakeXmlDate(lastNotification);
152             LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
153
154             XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
155             this.lastNotification = null;
156             notificationLatch = new CountDownLatch(1);
157         }
158
159         public String awaitUntillNotification(String xml) {
160             // FIXME: use awaitility
161             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
162                 fail("Timed out waiting for notification for: " + xml);
163             }
164             return lastNotification;
165         }
166
167         public void resetLatch() {
168             notificationLatch = new CountDownLatch(1);
169         }
170     }
171
172     static String withFakeDate(final String in) throws JSONException {
173         final JSONObject doc = new JSONObject(in);
174         final JSONObject notification =
175                 doc.getJSONObject("urn-ietf-params-xml-ns-netconf-notification-1.0:notification");
176         if (notification == null) {
177             return in;
178         }
179         notification.put("event-time", "someDate");
180         return doc.toString();
181     }
182
183     static String withFakeXmlDate(String in) {
184         return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
185     }
186
187     private String getNotifJson(final String path) throws IOException, URISyntaxException, JSONException {
188         final URL url = getClass().getResource(path);
189         final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
190         return withFakeDate(new String(bytes, StandardCharsets.UTF_8));
191     }
192
193     private String getResultXml(final String path) throws IOException, URISyntaxException, JSONException {
194         final URL url = getClass().getResource(path);
195         final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
196         return withFakeXmlDate(new String(bytes, StandardCharsets.UTF_8));
197     }
198
199     @Test
200     public void testJsonNotifsLeaves() throws Exception {
201         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
202                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, true, false);
203         adapter.setCloseVars(domDataBroker, schemaContextHandler);
204
205         final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
206                 .getInstance(DOMDataTreeChangeService.class);
207         final DOMDataTreeIdentifier root =
208                 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
209         changeService.registerDataTreeChangeListener(root, adapter);
210
211         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
212         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
213         final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
214                 .child(MyList1.class, new MyList1Key("Althea"));
215         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
216         writeTransaction.commit();
217         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
218
219         writeTransaction = dataBroker.newWriteOnlyTransaction();
220         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
221         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
222         writeTransaction.commit();
223         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
224
225         writeTransaction = dataBroker.newWriteOnlyTransaction();
226         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
227         writeTransaction.commit();
228         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
229     }
230
231     @Test
232     public void testJsonNotifs() throws Exception {
233         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
234                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, false);
235         adapter.setCloseVars(domDataBroker, schemaContextHandler);
236
237         final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
238                 .getInstance(DOMDataTreeChangeService.class);
239         final DOMDataTreeIdentifier root =
240                 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
241         changeService.registerDataTreeChangeListener(root, adapter);
242
243         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
244         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
245         final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
246                 .child(MyList1.class, new MyList1Key("Althea"));
247         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
248         writeTransaction.commit();
249         adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE));
250
251         writeTransaction = dataBroker.newWriteOnlyTransaction();
252         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
253         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
254         writeTransaction.commit();
255         adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE));
256
257         writeTransaction = dataBroker.newWriteOnlyTransaction();
258         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
259         writeTransaction.commit();
260         adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
261     }
262
263     @Test
264     public void testJsonNotifsWithoutData() throws Exception {
265         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
266                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, true);
267         adapter.setCloseVars(domDataBroker, schemaContextHandler);
268
269         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
270                 .getInstance(DOMDataTreeChangeService.class);
271         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
272         changeService.registerDataTreeChangeListener(root, adapter);
273         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
274         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
275         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
276                 .child(MyList1.class, new MyList1Key("Althea"));
277         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
278         writeTransaction.commit();
279         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_CREATE));
280
281         writeTransaction = dataBroker.newWriteOnlyTransaction();
282         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
283         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
284         writeTransaction.commit();
285         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_UPDATE));
286
287         writeTransaction = dataBroker.newWriteOnlyTransaction();
288         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
289         writeTransaction.commit();
290         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
291     }
292
293     @Test
294     public void testXmlNotifications() throws Exception {
295         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
296                 NotificationOutputTypeGrouping.NotificationOutputType.XML, false, false);
297         adapter.setCloseVars(domDataBroker, schemaContextHandler);
298
299         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
300                 .getInstance(DOMDataTreeChangeService.class);
301         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
302         changeService.registerDataTreeChangeListener(root, adapter);
303         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
304         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
305         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
306                 .child(MyList1.class, new MyList1Key("Althea"));
307         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
308         writeTransaction.commit();
309         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CREATE));
310
311         writeTransaction = dataBroker.newWriteOnlyTransaction();
312         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
313         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
314         writeTransaction.commit();
315         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_UPDATE));
316
317         writeTransaction = dataBroker.newWriteOnlyTransaction();
318         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
319         writeTransaction.commit();
320         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_DEL));
321     }
322
323     @Test
324     public void testXmlSkipData() throws Exception {
325         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
326                 NotificationOutputTypeGrouping.NotificationOutputType.XML, false, true);
327         adapter.setCloseVars(domDataBroker, schemaContextHandler);
328
329         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
330                 .getInstance(DOMDataTreeChangeService.class);
331         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
332         changeService.registerDataTreeChangeListener(root, adapter);
333         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
334         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
335         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
336                 .child(MyList1.class, new MyList1Key("Althea"));
337         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
338         writeTransaction.commit();
339         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_CREATE));
340
341         writeTransaction = dataBroker.newWriteOnlyTransaction();
342         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
343         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
344         writeTransaction.commit();
345         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_UPDATE));
346
347         writeTransaction = dataBroker.newWriteOnlyTransaction();
348         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
349         writeTransaction.commit();
350         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_DELETE));
351     }
352
353     @Test
354     public void testXmlLeavesOnly() throws Exception {
355         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
356                 NotificationOutputTypeGrouping.NotificationOutputType.XML, true, false);
357         adapter.setCloseVars(domDataBroker, schemaContextHandler);
358
359         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
360                 .getInstance(DOMDataTreeChangeService.class);
361         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
362         changeService.registerDataTreeChangeListener(root, adapter);
363         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
364         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
365         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
366                 .child(MyList1.class, new MyList1Key("Althea"));
367         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
368         writeTransaction.commit();
369         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_CREATE));
370
371         writeTransaction = dataBroker.newWriteOnlyTransaction();
372         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
373         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
374         writeTransaction.commit();
375         adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_UPDATE));
376
377         writeTransaction = dataBroker.newWriteOnlyTransaction();
378         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
379         writeTransaction.commit();
380
381         // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
382         final String notification = adapter.awaitUntillNotification("");
383         assertTrue(notification.contains("instance-identifier-patch-module:my-leaf12"));
384         assertTrue(notification.contains("instance-identifier-patch-module:my-leaf11"));
385         assertTrue(notification.contains("instance-identifier-patch-module:name"));
386     }
387 }