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