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;
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.LeafNodesOnlyParam;
39 import org.opendaylight.restconf.nb.rfc8040.NotificationQueryParams;
40 import org.opendaylight.restconf.nb.rfc8040.SkipNotificationDataParam;
41 import org.opendaylight.restconf.nb.rfc8040.StartTimeParam;
42 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
43 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.PatchCont1Builder;
44 import org.opendaylight.yang.gen.v1.augment.instance.identifier.patch.module.rev220218.patch.cont.patch.choice1.PatchCase1Builder;
45 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
46 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchContBuilder;
47 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
48 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder;
49 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key;
50 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping.NotificationOutputType;
51 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
54 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
55 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
56 import org.skyscreamer.jsonassert.JSONAssert;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59 import org.xmlunit.assertj.XmlAssert;
61 public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
62 private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
64 private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json";
65 private static final String JSON_NOTIF_LEAVES_UPDATE = "/listener-adapter-test/notif-leaves-update.json";
66 private static final String JSON_NOTIF_LEAVES_DEL = "/listener-adapter-test/notif-leaves-del.json";
67 private static final String JSON_NOTIF_CREATE = "/listener-adapter-test/notif-create.json";
68 private static final String JSON_NOTIF_UPDATE = "/listener-adapter-test/notif-update.json";
69 private static final String JSON_NOTIF_DEL = "/listener-adapter-test/notif-del.json";
70 private static final String JSON_NOTIF_WITHOUT_DATA_CREATE =
71 "/listener-adapter-test/notif-without-data-create.json";
72 private static final String JSON_NOTIF_WITHOUT_DATA_UPDATE =
73 "/listener-adapter-test/notif-without-data-update.json";
74 private static final String JSON_NOTIF_WITHOUT_DATA_DELETE =
75 "/listener-adapter-test/notif-without-data-del.json";
77 private static final String XML_NOTIF_CREATE = "/listener-adapter-test/notif-create.xml";
78 private static final String XML_NOTIF_UPDATE = "/listener-adapter-test/notif-update.xml";
79 private static final String XML_NOTIF_DEL = "/listener-adapter-test/notif-delete.xml";
81 private static final String XML_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.xml";
82 private static final String XML_NOTIF_LEAVES_UPDATE = "/listener-adapter-test/notif-leaves-update.xml";
83 private static final String XML_NOTIF_LEAVES_DEL = "/listener-adapter-test/notif-leaves-delete.xml";
85 private static final String XML_NOTIF_WITHOUT_DATA_CREATE =
86 "/listener-adapter-test/notif-without-data-create.xml";
87 private static final String XML_NOTIF_WITHOUT_DATA_UPDATE =
88 "/listener-adapter-test/notif-without-data-update.xml";
89 private static final String XML_NOTIF_WITHOUT_DATA_DELETE =
90 "/listener-adapter-test/notif-without-data-delete.xml";
92 private static final YangInstanceIdentifier PATCH_CONT_YIID =
93 YangInstanceIdentifier.create(new NodeIdentifier(PatchCont.QNAME));
95 private static EffectiveModelContext SCHEMA_CONTEXT;
97 private DataBroker dataBroker;
98 private DOMDataBroker domDataBroker;
99 private SchemaContextHandler schemaContextHandler;
102 public static void beforeClass() {
103 SCHEMA_CONTEXT = YangParserTestUtils.parseYangResourceDirectory(
104 "/instanceidentifier/yang");
108 public static void afterClass() {
109 SCHEMA_CONTEXT = null;
113 public void setUp() throws Exception {
114 dataBroker = getDataBroker();
115 domDataBroker = getDomBroker();
117 schemaContextHandler = new SchemaContextHandler(domDataBroker, mock(DOMSchemaService.class));
118 schemaContextHandler.onModelContextUpdated(SCHEMA_CONTEXT);
121 class ListenerAdapterTester extends ListenerAdapter {
123 private volatile String lastNotification;
124 private CountDownLatch notificationLatch = new CountDownLatch(1);
126 ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName,
127 final NotificationOutputType outputType,
128 final boolean leafNodesOnly, final boolean skipNotificationData) {
129 super(path, streamName, outputType);
130 setQueryParams(NotificationQueryParams.of(StartTimeParam.forUriValue("1970-01-01T00:00:00Z"), null, null,
131 LeafNodesOnlyParam.of(leafNodesOnly), SkipNotificationDataParam.of(skipNotificationData)));
135 protected void post(final String data) {
136 lastNotification = data;
137 notificationLatch.countDown();
140 public void assertGot(final String json) throws JSONException {
141 // FIXME: use awaitility
142 if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
143 fail("Timed out waiting for notification for: " + json);
146 LOG.info("lastNotification: {}", lastNotification);
147 final String withFakeDate = withFakeDate(lastNotification);
148 LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
150 JSONAssert.assertEquals(json, withFakeDate, false);
151 lastNotification = null;
152 notificationLatch = new CountDownLatch(1);
155 public void assertXmlSimilar(final String xml) {
156 awaitUntillNotification(xml);
158 LOG.info("lastNotification: {}", lastNotification);
159 final String withFakeDate = withFakeXmlDate(lastNotification);
160 LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
162 XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
163 lastNotification = null;
164 notificationLatch = new CountDownLatch(1);
167 public String awaitUntillNotification(final String xml) {
168 // FIXME: use awaitility
169 if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
170 fail("Timed out waiting for notification for: " + xml);
172 return lastNotification;
175 public void resetLatch() {
176 notificationLatch = new CountDownLatch(1);
180 static String withFakeDate(final String in) throws JSONException {
181 final JSONObject doc = new JSONObject(in);
182 final JSONObject notification =
183 doc.getJSONObject("urn-ietf-params-xml-ns-netconf-notification-1.0:notification");
184 if (notification == null) {
187 notification.put("event-time", "someDate");
188 return doc.toString();
191 static String withFakeXmlDate(final String in) {
192 return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
195 private String getNotifJson(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 withFakeDate(new String(bytes, StandardCharsets.UTF_8));
201 private String getResultXml(final String path) throws IOException, URISyntaxException, JSONException {
202 final URL url = getClass().getResource(path);
203 final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
204 return withFakeXmlDate(new String(bytes, StandardCharsets.UTF_8));
208 public void testJsonNotifsLeaves() throws Exception {
209 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON,
211 adapter.setCloseVars(domDataBroker, schemaContextHandler);
213 final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
214 .getInstance(DOMDataTreeChangeService.class);
215 final DOMDataTreeIdentifier root =
216 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
217 changeService.registerDataTreeChangeListener(root, adapter);
219 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
220 final InstanceIdentifier<PatchCont> iid = InstanceIdentifier.create(PatchCont.class);
221 PatchContBuilder builder =
222 new PatchContBuilder()
224 new PatchCont1Builder()
225 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceLeaf").build())
226 .setLeaf1("AugmentLeaf").build())
228 Map.of(new MyList1Key("Althea"),
229 new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build())
231 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
232 writeTransaction.commit();
233 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
235 writeTransaction = dataBroker.newWriteOnlyTransaction();
236 builder = new PatchContBuilder()
238 new PatchCont1Builder()
239 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceUpdate").build())
240 .setLeaf1("AugmentLeaf").build())
242 Map.of(new MyList1Key("Althea"),
243 new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build())
245 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
246 writeTransaction.commit();
247 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
249 writeTransaction = dataBroker.newWriteOnlyTransaction();
250 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
251 writeTransaction.commit();
252 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
256 public void testJsonNotifs() throws Exception {
257 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON,
259 adapter.setCloseVars(domDataBroker, schemaContextHandler);
261 final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
262 .getInstance(DOMDataTreeChangeService.class);
263 final DOMDataTreeIdentifier root =
264 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
265 changeService.registerDataTreeChangeListener(root, adapter);
267 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
268 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
269 final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
270 .child(MyList1.class, new MyList1Key("Althea"));
271 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
272 writeTransaction.commit();
273 adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE));
275 writeTransaction = dataBroker.newWriteOnlyTransaction();
276 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
277 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
278 writeTransaction.commit();
279 adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE));
281 writeTransaction = dataBroker.newWriteOnlyTransaction();
282 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
283 writeTransaction.commit();
284 adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
288 public void testJsonNotifsWithoutData() throws Exception {
289 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.JSON,
291 adapter.setCloseVars(domDataBroker, schemaContextHandler);
293 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
294 .getInstance(DOMDataTreeChangeService.class);
295 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
296 changeService.registerDataTreeChangeListener(root, adapter);
297 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
298 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
299 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
300 .child(MyList1.class, new MyList1Key("Althea"));
301 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
302 writeTransaction.commit();
303 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_CREATE));
305 writeTransaction = dataBroker.newWriteOnlyTransaction();
306 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
307 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
308 writeTransaction.commit();
309 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_UPDATE));
311 writeTransaction = dataBroker.newWriteOnlyTransaction();
312 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
313 writeTransaction.commit();
314 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
318 public void testXmlNotifications() throws Exception {
319 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML,
321 adapter.setCloseVars(domDataBroker, schemaContextHandler);
323 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
324 .getInstance(DOMDataTreeChangeService.class);
325 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
326 changeService.registerDataTreeChangeListener(root, adapter);
327 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
328 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
329 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
330 .child(MyList1.class, new MyList1Key("Althea"));
331 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
332 writeTransaction.commit();
333 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CREATE));
335 writeTransaction = dataBroker.newWriteOnlyTransaction();
336 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
337 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
338 writeTransaction.commit();
339 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_UPDATE));
341 writeTransaction = dataBroker.newWriteOnlyTransaction();
342 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
343 writeTransaction.commit();
344 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_DEL));
348 public void testXmlSkipData() throws Exception {
349 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML,
351 adapter.setCloseVars(domDataBroker, schemaContextHandler);
353 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
354 .getInstance(DOMDataTreeChangeService.class);
355 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
356 changeService.registerDataTreeChangeListener(root, adapter);
357 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
358 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
359 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
360 .child(MyList1.class, new MyList1Key("Althea"));
361 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
362 writeTransaction.commit();
363 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_CREATE));
365 writeTransaction = dataBroker.newWriteOnlyTransaction();
366 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
367 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
368 writeTransaction.commit();
369 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_UPDATE));
371 writeTransaction = dataBroker.newWriteOnlyTransaction();
372 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
373 writeTransaction.commit();
374 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_DELETE));
378 public void testXmlLeavesOnly() throws Exception {
379 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey", NotificationOutputType.XML,
381 adapter.setCloseVars(domDataBroker, schemaContextHandler);
383 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
384 .getInstance(DOMDataTreeChangeService.class);
385 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
386 changeService.registerDataTreeChangeListener(root, adapter);
387 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
388 final InstanceIdentifier<PatchCont> iid = InstanceIdentifier.create(PatchCont.class);
389 PatchContBuilder builder = new PatchContBuilder()
391 new PatchCont1Builder()
392 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceLeaf").build())
393 .setLeaf1("AugmentLeaf").build())
395 Map.of(new MyList1Key("Althea"),
396 new MyList1Builder().setMyLeaf11("Jed").setName("Althea").build())
398 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
399 writeTransaction.commit();
400 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_CREATE));
402 writeTransaction = dataBroker.newWriteOnlyTransaction();
403 builder = new PatchContBuilder()
405 new PatchCont1Builder()
406 .setPatchChoice1(new PatchCase1Builder().setCaseLeaf1("ChoiceUpdate").build())
407 .setLeaf1("AugmentLeaf").build())
409 Map.of(new MyList1Key("Althea"),
410 new MyList1Builder().setMyLeaf12("Bertha").setName("Althea").build())
412 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
413 writeTransaction.commit();
414 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_UPDATE));
416 writeTransaction = dataBroker.newWriteOnlyTransaction();
417 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
418 writeTransaction.commit();
420 // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
421 final String notification = adapter.awaitUntillNotification("");
422 assertTrue(notification.contains("instance-identifier-patch-module:my-leaf12"));
423 assertTrue(notification.contains("instance-identifier-patch-module:my-leaf11"));
424 assertTrue(notification.contains("instance-identifier-patch-module:name"));
425 assertTrue(notification.contains("augment-instance-identifier-patch-module:case-leaf1"));
426 assertTrue(notification.contains("augment-instance-identifier-patch-module:leaf1"));