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