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.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;
55 public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
56 private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
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";
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";
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";
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";
86 private static final YangInstanceIdentifier PATCH_CONT_YIID =
87 YangInstanceIdentifier.create(new NodeIdentifier(PatchCont.QNAME));
89 private static EffectiveModelContext SCHEMA_CONTEXT;
91 private DataBroker dataBroker;
92 private DOMDataBroker domDataBroker;
93 private SchemaContextHandler schemaContextHandler;
96 public static void beforeClass() {
97 SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource(
98 "/instanceidentifier/yang/instance-identifier-patch-module.yang");
102 public static void afterClass() {
103 SCHEMA_CONTEXT = null;
107 public void setUp() throws Exception {
108 dataBroker = getDataBroker();
109 domDataBroker = getDomBroker();
111 schemaContextHandler = new SchemaContextHandler(domDataBroker, mock(DOMSchemaService.class));
112 schemaContextHandler.onModelContextUpdated(SCHEMA_CONTEXT);
115 class ListenerAdapterTester extends ListenerAdapter {
117 private volatile String lastNotification;
118 private CountDownLatch notificationLatch = new CountDownLatch(1);
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));
129 protected void post(final String data) {
130 lastNotification = data;
131 notificationLatch.countDown();
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);
140 LOG.info("lastNotification: {}", lastNotification);
141 final String withFakeDate = withFakeDate(lastNotification);
142 LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
144 JSONAssert.assertEquals(json, withFakeDate, false);
145 lastNotification = null;
146 notificationLatch = new CountDownLatch(1);
149 public void assertXmlSimilar(final String xml) {
150 awaitUntillNotification(xml);
152 LOG.info("lastNotification: {}", lastNotification);
153 final String withFakeDate = withFakeXmlDate(lastNotification);
154 LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
156 XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
157 lastNotification = null;
158 notificationLatch = new CountDownLatch(1);
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);
166 return lastNotification;
169 public void resetLatch() {
170 notificationLatch = new CountDownLatch(1);
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) {
181 notification.put("event-time", "someDate");
182 return doc.toString();
185 static String withFakeXmlDate(final String in) {
186 return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
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));
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));
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);
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);
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));
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));
227 writeTransaction = dataBroker.newWriteOnlyTransaction();
228 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
229 writeTransaction.commit();
230 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
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);
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);
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));
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));
259 writeTransaction = dataBroker.newWriteOnlyTransaction();
260 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
261 writeTransaction.commit();
262 adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
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);
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));
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));
289 writeTransaction = dataBroker.newWriteOnlyTransaction();
290 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
291 writeTransaction.commit();
292 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
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);
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));
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));
319 writeTransaction = dataBroker.newWriteOnlyTransaction();
320 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
321 writeTransaction.commit();
322 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_DEL));
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);
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));
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));
349 writeTransaction = dataBroker.newWriteOnlyTransaction();
350 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
351 writeTransaction.commit();
352 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_DELETE));
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);
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));
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));
379 writeTransaction = dataBroker.newWriteOnlyTransaction();
380 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
381 writeTransaction.commit();
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"));