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