2 * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
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
8 package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
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;
15 import com.google.common.util.concurrent.Uninterruptibles;
16 import java.io.IOException;
17 import java.net.URISyntaxException;
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;
54 public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
55 private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
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";
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";
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";
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";
85 private static final YangInstanceIdentifier PATCH_CONT_YIID =
86 YangInstanceIdentifier.create(new NodeIdentifier(PatchCont.QNAME));
88 private static EffectiveModelContext SCHEMA_CONTEXT;
90 private DataBroker dataBroker;
91 private DOMDataBroker domDataBroker;
92 private SchemaContextHandler schemaContextHandler;
95 public static void beforeClass() {
96 SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource(
97 "/instanceidentifier/yang/instance-identifier-patch-module.yang");
101 public static void afterClass() {
102 SCHEMA_CONTEXT = null;
106 public void setUp() throws Exception {
107 dataBroker = getDataBroker();
108 domDataBroker = getDomBroker();
110 schemaContextHandler = new SchemaContextHandler(domDataBroker, mock(DOMSchemaService.class));
111 schemaContextHandler.onModelContextUpdated(SCHEMA_CONTEXT);
114 class ListenerAdapterTester extends ListenerAdapter {
116 private volatile String lastNotification;
117 private CountDownLatch notificationLatch = new CountDownLatch(1);
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);
127 protected void post(final String data) {
128 this.lastNotification = data;
129 notificationLatch.countDown();
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);
138 LOG.info("lastNotification: {}", lastNotification);
139 final String withFakeDate = withFakeDate(lastNotification);
140 LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
142 JSONAssert.assertEquals(json, withFakeDate, false);
143 this.lastNotification = null;
144 notificationLatch = new CountDownLatch(1);
147 public void assertXmlSimilar(String xml) {
148 awaitUntillNotification(xml);
150 LOG.info("lastNotification: {}", lastNotification);
151 final String withFakeDate = withFakeXmlDate(lastNotification);
152 LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
154 XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
155 this.lastNotification = null;
156 notificationLatch = new CountDownLatch(1);
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);
164 return lastNotification;
167 public void resetLatch() {
168 notificationLatch = new CountDownLatch(1);
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) {
179 notification.put("event-time", "someDate");
180 return doc.toString();
183 static String withFakeXmlDate(String in) {
184 return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
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));
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));
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);
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);
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));
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));
225 writeTransaction = dataBroker.newWriteOnlyTransaction();
226 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
227 writeTransaction.commit();
228 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
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);
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);
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));
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));
257 writeTransaction = dataBroker.newWriteOnlyTransaction();
258 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
259 writeTransaction.commit();
260 adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
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);
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));
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));
287 writeTransaction = dataBroker.newWriteOnlyTransaction();
288 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
289 writeTransaction.commit();
290 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
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);
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));
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));
317 writeTransaction = dataBroker.newWriteOnlyTransaction();
318 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
319 writeTransaction.commit();
320 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_DEL));
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);
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));
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));
347 writeTransaction = dataBroker.newWriteOnlyTransaction();
348 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
349 writeTransaction.commit();
350 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_DELETE));
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);
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));
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));
377 writeTransaction = dataBroker.newWriteOnlyTransaction();
378 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
379 writeTransaction.commit();
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"));