Extend Websocket streams for data-less notifications
[netconf.git] / restconf / restconf-nb-rfc8040 / src / test / java / org / opendaylight / restconf / nb / rfc8040 / streams / listeners / ListenerAdapterTest.java
1 /*
2  * Copyright (c) 2017 Red Hat, Inc. and others. All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.streams.listeners;
9
10 import static java.time.Instant.EPOCH;
11 import static org.junit.Assert.fail;
12
13 import com.google.common.util.concurrent.Uninterruptibles;
14 import java.io.IOException;
15 import java.net.URISyntaxException;
16 import java.net.URL;
17 import java.nio.charset.StandardCharsets;
18 import java.nio.file.Files;
19 import java.nio.file.Paths;
20 import java.util.concurrent.CountDownLatch;
21 import java.util.concurrent.TimeUnit;
22 import org.json.JSONObject;
23 import org.junit.AfterClass;
24 import org.junit.Before;
25 import org.junit.BeforeClass;
26 import org.junit.Test;
27 import org.mockito.Mockito;
28 import org.opendaylight.mdsal.binding.api.DataBroker;
29 import org.opendaylight.mdsal.binding.api.WriteTransaction;
30 import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractConcurrentDataBrokerTest;
31 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
32 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
33 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
34 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
35 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
36 import org.opendaylight.restconf.nb.rfc8040.handlers.SchemaContextHandler;
37 import org.opendaylight.restconf.nb.rfc8040.handlers.TransactionChainHandler;
38 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.PatchCont;
39 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1;
40 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Builder;
41 import org.opendaylight.yang.gen.v1.instance.identifier.patch.module.rev151121.patch.cont.MyList1Key;
42 import org.opendaylight.yang.gen.v1.urn.sal.restconf.event.subscription.rev140708.NotificationOutputTypeGrouping;
43 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
46 import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
47 import org.skyscreamer.jsonassert.JSONAssert;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 public class ListenerAdapterTest extends AbstractConcurrentDataBrokerTest {
52     private static final Logger LOG = LoggerFactory.getLogger(ListenerAdapterTest.class);
53
54     private static final String JSON_NOTIF_LEAVES_CREATE = "/listener-adapter-test/notif-leaves-create.json";
55     private static final String JSON_NOTIF_LEAVES_UPDATE =  "/listener-adapter-test/notif-leaves-update.json";
56     private static final String JSON_NOTIF_LEAVES_DEL =  "/listener-adapter-test/notif-leaves-del.json";
57     private static final String JSON_NOTIF_CREATE = "/listener-adapter-test/notif-create.json";
58     private static final String JSON_NOTIF_UPDATE = "/listener-adapter-test/notif-update.json";
59     private static final String JSON_NOTIF_DEL = "/listener-adapter-test/notif-del.json";
60     private static final String JSON_NOTIF_WITHOUT_DATA_CREATE =
61             "/listener-adapter-test/notif-without-data-create.json";
62     private static final String JSON_NOTIF_WITHOUT_DATA_UPDATE =
63             "/listener-adapter-test/notif-without-data-update.json";
64     private static final String JSON_NOTIF_WITHOUT_DATA_DELETE =
65             "/listener-adapter-test/notif-without-data-del.json";
66
67     private static final YangInstanceIdentifier PATCH_CONT_YIID =
68             YangInstanceIdentifier.create(new YangInstanceIdentifier.NodeIdentifier(PatchCont.QNAME));
69
70     private static EffectiveModelContext SCHEMA_CONTEXT;
71
72     private DataBroker dataBroker;
73     private DOMDataBroker domDataBroker;
74     private TransactionChainHandler transactionChainHandler;
75     private SchemaContextHandler schemaContextHandler;
76
77     @BeforeClass
78     public static void beforeClass() {
79         SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource(
80                 "/instanceidentifier/yang/instance-identifier-patch-module.yang");
81     }
82
83     @AfterClass
84     public static void afterClass() {
85         SCHEMA_CONTEXT = null;
86     }
87
88     @Before
89     public void setUp() throws Exception {
90         dataBroker = getDataBroker();
91         domDataBroker = getDomBroker();
92
93         transactionChainHandler = new TransactionChainHandler(domDataBroker);
94         schemaContextHandler = new SchemaContextHandler(transactionChainHandler, Mockito.mock(DOMSchemaService.class));
95         schemaContextHandler.onModelContextUpdated(SCHEMA_CONTEXT);
96     }
97
98     class ListenerAdapterTester extends ListenerAdapter {
99
100         private volatile String lastNotification;
101         private CountDownLatch notificationLatch = new CountDownLatch(1);
102
103         ListenerAdapterTester(final YangInstanceIdentifier path, final String streamName,
104                               final NotificationOutputTypeGrouping.NotificationOutputType outputType,
105                               final boolean leafNodesOnly, final boolean skipNotificationData) {
106             super(path, streamName, outputType);
107             setQueryParams(EPOCH, null, null, leafNodesOnly, skipNotificationData);
108         }
109
110         @Override
111         protected void post(final String data) {
112             this.lastNotification = data;
113             notificationLatch.countDown();
114         }
115
116         public void assertGot(final String json) {
117             if (!Uninterruptibles.awaitUninterruptibly(notificationLatch, 5, TimeUnit.SECONDS)) {
118                 fail("Timed out waiting for notification for: " + json);
119             }
120
121             LOG.info("lastNotification: {}", lastNotification);
122             String withFakeDate = withFakeDate(lastNotification);
123             LOG.info("Comparing: \n{}\n{}", json, withFakeDate);
124
125             JSONAssert.assertEquals(json, withFakeDate, false);
126             this.lastNotification = null;
127             notificationLatch = new CountDownLatch(1);
128         }
129     }
130
131     static String withFakeDate(final String in) {
132         JSONObject doc = new JSONObject(in);
133         JSONObject notification = doc.getJSONObject("notification");
134         if (notification == null) {
135             return in;
136         }
137         notification.put("eventTime", "someDate");
138         return doc.toString();
139     }
140
141     private String getNotifJson(final String path) throws IOException, URISyntaxException {
142         URL url = getClass().getResource(path);
143         byte[] bytes = Files.readAllBytes(Paths.get(url.toURI()));
144         return withFakeDate(new String(bytes, StandardCharsets.UTF_8));
145     }
146
147     @Test
148     public void testJsonNotifsLeaves() throws Exception {
149         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
150                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, true, false);
151         adapter.setCloseVars(transactionChainHandler, schemaContextHandler);
152
153         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
154                 .getInstance(DOMDataTreeChangeService.class);
155         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
156         changeService.registerDataTreeChangeListener(root, adapter);
157
158         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
159         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
160         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
161                 .child(MyList1.class, new MyList1Key("Althea"));
162         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
163         writeTransaction.commit();
164         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_CREATE));
165
166         writeTransaction = dataBroker.newWriteOnlyTransaction();
167         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
168         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
169         writeTransaction.commit();
170         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_UPDATE));
171
172         writeTransaction = dataBroker.newWriteOnlyTransaction();
173         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
174         writeTransaction.commit();
175         adapter.assertGot(getNotifJson(JSON_NOTIF_LEAVES_DEL));
176     }
177
178     @Test
179     public void testJsonNotifs() throws Exception {
180         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
181                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, false);
182         adapter.setCloseVars(transactionChainHandler, schemaContextHandler);
183
184         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
185                 .getInstance(DOMDataTreeChangeService.class);
186         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
187         changeService.registerDataTreeChangeListener(root, adapter);
188
189         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
190         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
191         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
192                 .child(MyList1.class, new MyList1Key("Althea"));
193         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
194         writeTransaction.commit();
195         adapter.assertGot(getNotifJson(JSON_NOTIF_CREATE));
196
197         writeTransaction = dataBroker.newWriteOnlyTransaction();
198         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
199         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
200         writeTransaction.commit();
201         adapter.assertGot(getNotifJson(JSON_NOTIF_UPDATE));
202
203         writeTransaction = dataBroker.newWriteOnlyTransaction();
204         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
205         writeTransaction.commit();
206         adapter.assertGot(getNotifJson(JSON_NOTIF_DEL));
207     }
208
209     @Test
210     public void testJsonNotifsWithoutData() throws Exception {
211         ListenerAdapterTester adapter = new ListenerAdapterTester(PATCH_CONT_YIID, "Casey",
212                 NotificationOutputTypeGrouping.NotificationOutputType.JSON, false, true);
213         adapter.setCloseVars(transactionChainHandler, schemaContextHandler);
214
215         DOMDataTreeChangeService changeService = domDataBroker.getExtensions()
216                 .getInstance(DOMDataTreeChangeService.class);
217         DOMDataTreeIdentifier root = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, PATCH_CONT_YIID);
218         changeService.registerDataTreeChangeListener(root, adapter);
219         WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
220         MyList1Builder builder = new MyList1Builder().setMyLeaf11("Jed").setName("Althea");
221         InstanceIdentifier<MyList1> iid = InstanceIdentifier.create(PatchCont.class)
222                 .child(MyList1.class, new MyList1Key("Althea"));
223         writeTransaction.mergeParentStructurePut(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
224         writeTransaction.commit();
225         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_CREATE));
226
227         writeTransaction = dataBroker.newWriteOnlyTransaction();
228         builder = new MyList1Builder().withKey(new MyList1Key("Althea")).setMyLeaf12("Bertha");
229         writeTransaction.mergeParentStructureMerge(LogicalDatastoreType.CONFIGURATION, iid, builder.build());
230         writeTransaction.commit();
231         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_UPDATE));
232
233         writeTransaction = dataBroker.newWriteOnlyTransaction();
234         writeTransaction.delete(LogicalDatastoreType.CONFIGURATION, iid);
235         writeTransaction.commit();
236         adapter.assertGot(getNotifJson(JSON_NOTIF_WITHOUT_DATA_DELETE));
237     }
238 }