HONEYCOMB-359 - Wildcarded writers
[honeycomb.git] / infra / translate-impl / src / test / java / io / fd / honeycomb / translate / impl / write / registry / FlatWriterRegistryTest.java
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package io.fd.honeycomb.translate.impl.write.registry;
18
19 import static org.hamcrest.Matchers.hasSize;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertThat;
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 import static org.mockito.Matchers.any;
25 import static org.mockito.Mockito.doThrow;
26 import static org.mockito.Mockito.inOrder;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.verifyNoMoreInteractions;
31 import static org.mockito.Mockito.verifyZeroInteractions;
32 import static org.mockito.Mockito.when;
33
34 import com.google.common.collect.HashMultimap;
35 import com.google.common.collect.ImmutableMap;
36 import com.google.common.collect.ImmutableMultimap;
37 import com.google.common.collect.Multimap;
38 import io.fd.honeycomb.translate.util.DataObjects;
39 import io.fd.honeycomb.translate.util.DataObjects.DataObject1;
40 import io.fd.honeycomb.translate.util.DataObjects.DataObject2;
41 import io.fd.honeycomb.translate.write.DataObjectUpdate;
42 import io.fd.honeycomb.translate.write.WriteContext;
43 import io.fd.honeycomb.translate.write.WriteFailedException;
44 import io.fd.honeycomb.translate.write.Writer;
45 import io.fd.honeycomb.translate.write.registry.UpdateFailedException;
46 import io.fd.honeycomb.translate.write.registry.WriterRegistry;
47 import java.util.List;
48 import javax.annotation.Nonnull;
49 import javax.annotation.Nullable;
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.mockito.InOrder;
53 import org.mockito.Mock;
54 import org.mockito.MockitoAnnotations;
55 import org.mockito.stubbing.Answer;
56 import org.opendaylight.yangtools.yang.binding.DataObject;
57 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
58
59 public class FlatWriterRegistryTest {
60
61     @Mock
62     private Writer<DataObject1> writer1;
63     @Mock
64     private Writer<DataObject2> writer2;
65     @Mock
66     private Writer<DataObjects.DataObject3> writer3;
67     @Mock
68     private Writer<DataObjects.DataObject1ChildK> writer4;
69     @Mock
70     private WriteContext ctx;
71     @Mock
72     private WriteContext revertWriteContext;
73
74     @Before
75     public void setUp() throws Exception {
76         MockitoAnnotations.initMocks(this);
77         when(writer1.getManagedDataObjectType()).thenReturn(DataObject1.IID);
78         when(writer2.getManagedDataObjectType()).thenReturn(DataObject2.IID);
79         when(writer3.getManagedDataObjectType()).thenReturn(DataObjects.DataObject3.IID);
80         when(writer4.getManagedDataObjectType()).thenReturn(DataObjects.DataObject1ChildK.IID);
81         // TODO - HONEYCOMB-412 - thenCallRealMethod doest work with default methods
82         // https://stackoverflow.com/questions/27663252/can-you-make-mockito-1-10-17-work-with-default-methods-in-interfaces
83         when(writer1.canProcess(any())).thenAnswer(answerWithImpl());
84         when(writer2.canProcess(any())).thenAnswer(answerWithImpl());
85         when(writer3.canProcess(any())).thenAnswer(answerWithImpl());
86         when(writer4.canProcess(any())).thenAnswer(answerWithImpl());
87     }
88
89     private static Answer<Object> answerWithImpl() {
90         return invocationOnMock -> new CheckedMockWriter(Writer.class.cast(invocationOnMock.getMock())).canProcess(
91                 InstanceIdentifier.class.cast(invocationOnMock.getArguments()[0]));
92     }
93
94     @Test
95     public void testMultipleUpdatesForSingleWriter() throws Exception {
96         final FlatWriterRegistry flatWriterRegistry =
97                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
98
99         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
100         final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
101         final InstanceIdentifier<DataObject1> iid2 = InstanceIdentifier.create(DataObject1.class);
102         final DataObject1 dataObject = mock(DataObject1.class);
103         updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
104         updates.put(DataObject1.IID, DataObjectUpdate.create(iid2, dataObject, dataObject));
105         flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
106
107         verify(writer1).processModification(iid, dataObject, dataObject, ctx);
108         verify(writer1).processModification(iid2, dataObject, dataObject, ctx);
109         // Invoked when registry is being created
110         verifyNoMoreInteractions(writer1);
111         verifyZeroInteractions(writer2);
112     }
113
114     @Test
115     public void testMultipleUpdatesForMultipleWriters() throws Exception {
116         final FlatWriterRegistry flatWriterRegistry =
117                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
118
119         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
120         final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
121         final DataObject1 dataObject = mock(DataObject1.class);
122         updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
123         final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
124         final DataObject2 dataObject2 = mock(DataObject2.class);
125         updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, dataObject2, dataObject2));
126         flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
127
128         final InOrder inOrder = inOrder(writer1, writer2);
129         inOrder.verify(writer1).processModification(iid, dataObject, dataObject, ctx);
130         inOrder.verify(writer2).processModification(iid2, dataObject2, dataObject2, ctx);
131
132         // TODO - HONEYCOMB-412 -reintroduce verifyNoMoreInteractions and remove manual verify
133         // we are really interested just in invocations of processModification(),so adding specific verify to check that
134         verify(writer1,times(1)).processModification(any(),any(),any(),any());
135         verify(writer2,times(1)).processModification(any(),any(),any(),any());
136         //verifyNoMoreInteractions(writer1);
137         //verifyNoMoreInteractions(writer2);
138     }
139
140     @Test
141     public void testMultipleDeletesForMultipleWriters() throws Exception {
142         final FlatWriterRegistry flatWriterRegistry =
143                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
144
145         final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create();
146         final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
147         final DataObject1 dataObject = mock(DataObject1.class);
148         deletes.put(DataObject1.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid, dataObject, null)));
149         final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
150         final DataObject2 dataObject2 = mock(DataObject2.class);
151         deletes.put(
152                 DataObject2.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid2, dataObject2, null)));
153         flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(ImmutableMultimap.of(), deletes), ctx);
154
155         final InOrder inOrder = inOrder(writer1, writer2);
156         // Reversed order of invocation, first writer2 and then writer1
157         inOrder.verify(writer2).processModification(iid2, dataObject2, null, ctx);
158         inOrder.verify(writer1).processModification(iid, dataObject, null, ctx);
159
160         // TODO - HONEYCOMB-412 -reintroduce verifyNoMoreInteractions and remove manual verify
161         // we are really interested just in invocations of processModification(),so adding specific verify to check that
162         verify(writer1,times(1)).processModification(any(),any(),any(),any());
163         verify(writer2,times(1)).processModification(any(),any(),any(),any());
164         //verifyNoMoreInteractions(writer1);
165         //verifyNoMoreInteractions(writer2);
166     }
167
168     @Test
169     public void testMultipleUpdatesAndDeletesForMultipleWriters() throws Exception {
170         final FlatWriterRegistry flatWriterRegistry =
171                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
172
173         final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create();
174         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
175         final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
176         final DataObject1 dataObject = mock(DataObject1.class);
177         // Writer 1 delete
178         deletes.put(DataObject1.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid, dataObject, null)));
179         // Writer 1 update
180         updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
181         final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
182         final DataObject2 dataObject2 = mock(DataObject2.class);
183         // Writer 2 delete
184         deletes.put(
185                 DataObject2.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid2, dataObject2, null)));
186         // Writer 2 update
187         updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, dataObject2, dataObject2));
188         flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, deletes), ctx);
189
190         final InOrder inOrder = inOrder(writer1, writer2);
191         // Reversed order of invocation, first writer2 and then writer1 for deletes
192         inOrder.verify(writer2).processModification(iid2, dataObject2, null, ctx);
193         inOrder.verify(writer1).processModification(iid, dataObject, null, ctx);
194         // Then also updates are processed
195         inOrder.verify(writer1).processModification(iid, dataObject, dataObject, ctx);
196         inOrder.verify(writer2).processModification(iid2, dataObject2, dataObject2, ctx);
197
198         // TODO - HONEYCOMB-412 -reintroduce verifyNoMoreInteractions and remove manual verify
199         // we are really interested just in invocations of processModification(),so adding specific verify to check that
200         verify(writer1,times(2)).processModification(any(),any(),any(),any());
201         verify(writer2,times(2)).processModification(any(),any(),any(),any());
202         //verifyNoMoreInteractions(writer1);
203         //verifyNoMoreInteractions(writer2);
204     }
205
206     @Test(expected = IllegalArgumentException.class)
207     public void testMultipleUpdatesOneMissing() throws Exception {
208         final FlatWriterRegistry flatWriterRegistry =
209                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1));
210
211         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
212         addUpdate(updates, DataObject1.class);
213         addUpdate(updates, DataObject2.class);
214         flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
215     }
216
217     @Test
218     public void testMultipleUpdatesFirstFailing() throws Exception {
219         final FlatWriterRegistry flatWriterRegistry =
220                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
221
222         // Writer1 always fails
223         doThrow(new RuntimeException()).when(writer1)
224                 .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
225
226         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
227         addUpdate(updates, DataObject1.class);
228         addUpdate(updates, DataObject2.class);
229
230         try {
231             flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
232             fail("Bulk update should have failed on writer1 with UpdateFailedException");
233         } catch (UpdateFailedException e) {
234             assertThat(e.getProcessed(), hasSize(0));// very first update failed
235         }
236     }
237
238     @Test
239     public void testMultipleUpdatesSecondFailing() throws Exception {
240         final FlatWriterRegistry flatWriterRegistry =
241                 new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
242
243         // Writer2 always fails
244         doThrow(new RuntimeException()).when(writer2)
245                 .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
246
247         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
248         addUpdate(updates, DataObject1.class);
249         addUpdate(updates, DataObject2.class);
250
251         try {
252             flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
253             fail("Bulk update should have failed on writer1 with UpdateFailedException");
254         } catch (UpdateFailedException e) {
255             final List<DataObjectUpdate> alreadyProcessed = e.getProcessed();
256             assertThat(alreadyProcessed, hasSize(1));// very first update failed
257             assertEquals(updateData(DataObject1.class, DataObject1.IID),
258                     e.getProcessed().iterator().next());
259         }
260     }
261
262     @Test
263     public void testMultipleUpdatesLastFailing() throws Exception {
264         final FlatWriterRegistry flatWriterRegistry =
265                 new FlatWriterRegistry(
266                         ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2, DataObjects.DataObject3.IID, writer3));
267
268         // Writer1 always fails
269         doThrow(new RuntimeException()).when(writer3)
270                 .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
271
272         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
273         addUpdate(updates, DataObject1.class);
274         addUpdate(updates, DataObject2.class);
275         addUpdate(updates, DataObjects.DataObject3.class);
276
277         try {
278             flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
279             fail("Bulk update should have failed on writer1 with UpdateFailedException");
280         } catch (UpdateFailedException e) {
281             final List<DataObjectUpdate> alreadyProcessed = e.getProcessed();
282             assertEquals(2, alreadyProcessed.size());
283             assertTrue(alreadyProcessed.contains(updateData(DataObject1.class, DataObject1.IID)));
284             assertTrue(alreadyProcessed.contains(updateData(DataObject2.class, DataObject2.IID)));
285         }
286     }
287
288     @Test
289     public void testMutlipleUpdatesWithOneKeyedContainer() throws Exception {
290         final InstanceIdentifier internallyKeyedIdentifier = InstanceIdentifier.create(DataObject1.class)
291                 .child(DataObjects.DataObject1ChildK.class, new DataObjects.DataObject1ChildKey());
292
293         final FlatWriterRegistry flatWriterRegistry =
294                 new FlatWriterRegistry(
295                         ImmutableMap.of(DataObject1.IID, writer1, DataObjects.DataObject1ChildK.IID, writer4));
296
297         // Writer1 always fails
298         doThrow(new RuntimeException()).when(writer1)
299                 .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class),
300                         any(WriteContext.class));
301
302         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
303         addKeyedUpdate(updates, DataObjects.DataObject1ChildK.class);
304         addUpdate(updates, DataObject1.class);
305         try {
306             flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
307             fail("Bulk update should have failed on writer1 with UpdateFailedException");
308         } catch (UpdateFailedException e) {
309             assertTrue(e.getProcessed().isEmpty());
310         }
311     }
312
313     private <D extends DataObject> void addKeyedUpdate(final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates,
314                                                        final Class<D> type) throws Exception {
315         final InstanceIdentifier<D> iid = (InstanceIdentifier<D>) type.getDeclaredField("IID").get(null);
316         final InstanceIdentifier<D> keyedIid = (InstanceIdentifier<D>) type.getDeclaredField("INTERNALLY_KEYED_IID").get(null);
317         updates.put(iid, DataObjectUpdate.create(keyedIid, mock(type), mock(type)));
318     }
319
320     private <D extends DataObject> void addUpdate(final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates,
321                                                   final Class<D> type) throws Exception {
322         final InstanceIdentifier<D> iid = (InstanceIdentifier<D>) type.getDeclaredField("IID").get(null);
323         updates.put(iid, updateData(type, iid));
324     }
325
326     private static <D extends DataObject> DataObjectUpdate updateData(final Class<D> type,
327                                                                       final InstanceIdentifier<D> iid) {
328         return DataObjectUpdate.create(iid, mock(type), mock(type));
329     }
330
331     //TODO - HONEYCOMB-412 - remove after
332     /**
333      * Used to utilize default implementation of canProcess()
334      * */
335     static class CheckedMockWriter implements Writer{
336
337         private final Writer mockedWriter;
338
339         CheckedMockWriter(final Writer mockedWriter) {
340             this.mockedWriter = mockedWriter;
341         }
342
343         @Override
344         public void processModification(@Nonnull final InstanceIdentifier id, @Nullable final DataObject dataBefore,
345                                         @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx)
346                 throws WriteFailedException {
347             mockedWriter.processModification(id,dataBefore,dataAfter,ctx);
348         }
349
350         @Override
351         public boolean supportsDirectUpdate() {
352             return mockedWriter.supportsDirectUpdate();
353         }
354
355         @Nonnull
356         @Override
357         public InstanceIdentifier getManagedDataObjectType() {
358             return mockedWriter.getManagedDataObjectType();
359         }
360     }
361 }