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.StartTimeParameter;
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(StartTimeParameter.forUriValue("1970-01-01T00:00:00Z"), null, null, leafNodesOnly,
124 skipNotificationData);
128 protected void post(final String data) {
129 lastNotification = data;
130 notificationLatch.countDown();
133 public void assertGot(final String json) throws JSONException {
134 // FIXME: use awaitility
135 if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
136 fail("Timed out waiting for notification for: " + json);
139 LOG.info("lastNotification: {}", lastNotification);
140 final String withFakeDate = withFakeDate(lastNotification);
141 LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
143 JSONAssert.assertEquals(json, withFakeDate, false);
144 lastNotification = null;
145 notificationLatch = new CountDownLatch(1);
148 public void assertXmlSimilar(final String xml) {
149 awaitUntillNotification(xml);
151 LOG.info("lastNotification: {}", lastNotification);
152 final String withFakeDate = withFakeXmlDate(lastNotification);
153 LOG.info("Comparing: \n{}\n{}", xml, withFakeDate);
155 XmlAssert.assertThat(xml).and(withFakeDate).ignoreWhitespace().ignoreChildNodesOrder().areSimilar();
156 lastNotification = null;
157 notificationLatch = new CountDownLatch(1);
160 public String awaitUntillNotification(final String xml) {
161 // FIXME: use awaitility
162 if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 500, TimeUnit.SECONDS)) {
163 fail("Timed out waiting for notification for: " + xml);
165 return lastNotification;
168 public void resetLatch() {
169 notificationLatch = new CountDownLatch(1);
173 static String withFakeDate(final String in) throws JSONException {
174 final JSONObject doc = new JSONObject(in);
175 final JSONObject notification =
176 doc.getJSONObject("urn-ietf-params-xml-ns-netconf-notification-1.0:notification");
177 if (notification == null) {
180 notification.put("event-time", "someDate");
181 return doc.toString();
184 static String withFakeXmlDate(final String in) {
185 return in.replaceAll("<eventTime>.*</eventTime>", "<eventTime>someDate</eventTime>");
188 private String getNotifJson(final String path) throws IOException, URISyntaxException, JSONException {
189 final URL url = getClass().getResource(path);
190 final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
191 return withFakeDate(new String(bytes, StandardCharsets.UTF_8));
194 private String getResultXml(final String path) throws IOException, URISyntaxException, JSONException {
195 final URL url = getClass().getResource(path);
196 final byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
197 return withFakeXmlDate(new String(bytes, StandardCharsets.UTF_8));
201 public void testJsonNotifsLeaves() throws Exception {
202 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
203 NotificationOutputTypeGrouping.NotificationOutputType.JSON, true, false);
204 adapter.setCloseVars(domDataBroker, schemaContextHandler);
206 final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
207 .getInstance(DOMDataTreeChangeService.class);
208 final DOMDataTreeIdentifier root =
209 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
210 changeService.registerDataTreeChangeListener(root, adapter);
212 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
213 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
214 final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
215 .child(MyList1.class, new MyList1Key("Althea"));
216 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
217 writeTransaction.commit();
218 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
220 writeTransaction = dataBroker.newWriteOnlyTransaction();
221 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
222 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
223 writeTransaction.commit();
224 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
226 writeTransaction = dataBroker.newWriteOnlyTransaction();
227 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
228 writeTransaction.commit();
229 adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
233 public void testJsonNotifs() throws Exception {
234 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
235 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, false);
236 adapter.setCloseVars(domDataBroker, schemaContextHandler);
238 final DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
239 .getInstance(DOMDataTreeChangeService.class);
240 final DOMDataTreeIdentifier root =
241 new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
242 changeService.registerDataTreeChangeListener(root, adapter);
244 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
245 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
246 final InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
247 .child(MyList1.class, new MyList1Key("Althea"));
248 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
249 writeTransaction.commit();
250 adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE));
252 writeTransaction = dataBroker.newWriteOnlyTransaction();
253 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
254 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
255 writeTransaction.commit();
256 adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE));
258 writeTransaction = dataBroker.newWriteOnlyTransaction();
259 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
260 writeTransaction.commit();
261 adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
265 public void testJsonNotifsWithoutData() throws Exception {
266 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
267 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, true);
268 adapter.setCloseVars(domDataBroker, schemaContextHandler);
270 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
271 .getInstance(DOMDataTreeChangeService.class);
272 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
273 changeService.registerDataTreeChangeListener(root, adapter);
274 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
275 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
276 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
277 .child(MyList1.class, new MyList1Key("Althea"));
278 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
279 writeTransaction.commit();
280 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_CREATE));
282 writeTransaction = dataBroker.newWriteOnlyTransaction();
283 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
284 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
285 writeTransaction.commit();
286 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_UPDATE));
288 writeTransaction = dataBroker.newWriteOnlyTransaction();
289 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
290 writeTransaction.commit();
291 adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
295 public void testXmlNotifications() throws Exception {
296 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
297 NotificationOutputTypeGrouping.NotificationOutputType.XML, false, false);
298 adapter.setCloseVars(domDataBroker, schemaContextHandler);
300 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
301 .getInstance(DOMDataTreeChangeService.class);
302 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
303 changeService.registerDataTreeChangeListener(root, adapter);
304 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
305 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
306 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
307 .child(MyList1.class, new MyList1Key("Althea"));
308 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
309 writeTransaction.commit();
310 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_CREATE));
312 writeTransaction = dataBroker.newWriteOnlyTransaction();
313 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
314 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
315 writeTransaction.commit();
316 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_UPDATE));
318 writeTransaction = dataBroker.newWriteOnlyTransaction();
319 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
320 writeTransaction.commit();
321 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_DEL));
325 public void testXmlSkipData() throws Exception {
326 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
327 NotificationOutputTypeGrouping.NotificationOutputType.XML, false, true);
328 adapter.setCloseVars(domDataBroker, schemaContextHandler);
330 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
331 .getInstance(DOMDataTreeChangeService.class);
332 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
333 changeService.registerDataTreeChangeListener(root, adapter);
334 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
335 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
336 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
337 .child(MyList1.class, new MyList1Key("Althea"));
338 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
339 writeTransaction.commit();
340 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_CREATE));
342 writeTransaction = dataBroker.newWriteOnlyTransaction();
343 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
344 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
345 writeTransaction.commit();
346 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_UPDATE));
348 writeTransaction = dataBroker.newWriteOnlyTransaction();
349 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
350 writeTransaction.commit();
351 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_WITHOUT_DATA_DELETE));
355 public void testXmlLeavesOnly() throws Exception {
356 ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
357 NotificationOutputTypeGrouping.NotificationOutputType.XML, true, false);
358 adapter.setCloseVars(domDataBroker, schemaContextHandler);
360 DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
361 .getInstance(DOMDataTreeChangeService.class);
362 DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
363 changeService.registerDataTreeChangeListener(root, adapter);
364 WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
365 MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
366 InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
367 .child(MyList1.class, new MyList1Key("Althea"));
368 writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
369 writeTransaction.commit();
370 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_CREATE));
372 writeTransaction = dataBroker.newWriteOnlyTransaction();
373 builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
374 writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
375 writeTransaction.commit();
376 adapter.assertXmlSimilar(getResultXml(XML_NOTIF_LEAVES_UPDATE));
378 writeTransaction = dataBroker.newWriteOnlyTransaction();
379 writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
380 writeTransaction.commit();
382 // xmlunit cannot compare deeper children it seems out of the box so just check the iid encoding
383 final String notification = adapter.awaitUntillNotification("");
384 assertTrue(notification.contains("instance-identifier-patch-module:my-leaf12"));
385 assertTrue(notification.contains("instance-identifier-patch-module:my-leaf11"));
386 assertTrue(notification.contains("instance-identifier-patch-module:name"));