From 5947a575539402344e450fd34b03f555b84523be Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Tue, 12 Apr 2016 10:13:02 +0200 Subject: HONEYCOMB-9: Exception handling for VPP APIs Change-Id: Ic71a2ac3d01e88cb38596a24a12a7bf8ebf54da5 Signed-off-by: Marek Gradzki Signed-off-by: Maros Marsalek --- .../v3po/impl/data/VPPConfigDataTreeTest.java | 84 +++++---- .../v3po/impl/data/VppOperationalDataTreeTest.java | 79 ++++++++- .../v3po/impl/trans/ReadFailedExceptionTest.java | 53 ++++++ .../trans/w/util/DelegatingWriterRegistryTest.java | 189 +++++++++++++++++++++ 4 files changed, 357 insertions(+), 48 deletions(-) create mode 100644 v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java create mode 100644 v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java (limited to 'v3po/impl/src/test') diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java index 963df735b..8719a5356 100644 --- a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java +++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VPPConfigDataTreeTest.java @@ -44,8 +44,6 @@ import org.mockito.Mock; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Vxlan; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; @@ -58,7 +56,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot; -import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException; public class VPPConfigDataTreeTest { @@ -89,7 +86,8 @@ public class VPPConfigDataTreeTest { doReturn(node).when(snapshot).readNode(path); final VppDataTreeSnapshot vppDataTreeSnapshot = proxy.takeSnapshot(); - final CheckedFuture>, ReadFailedException> future = vppDataTreeSnapshot.read(path); + final CheckedFuture>, ReadFailedException> future = + vppDataTreeSnapshot.read(path); verify(dataTree).takeSnapshot(); verify(snapshot).readNode(path); @@ -132,9 +130,9 @@ public class VPPConfigDataTreeTest { // Verify all changes were processed: verify(vppWriter).update( - mapOf(dataBefore, Ethernet.class), - mapOf(dataAfter, Ethernet.class), - any(WriteContext.class)); + mapOf(dataBefore, Ethernet.class), + mapOf(dataAfter, Ethernet.class), + any(WriteContext.class)); // Verify modification was validated verify(dataTree).validate(modification); @@ -142,7 +140,8 @@ public class VPPConfigDataTreeTest { private Map, DataObject> mapOf(final DataObject dataBefore, final Class type) { return eq( - Collections., DataObject>singletonMap(InstanceIdentifier.create(type), dataBefore)); + Collections., DataObject>singletonMap(InstanceIdentifier.create(type), + dataBefore)); } private DataObject mockDataObject(final String name, final Class classToMock) { @@ -154,79 +153,77 @@ public class VPPConfigDataTreeTest { @Test public void testCommitUndoSuccessful() throws Exception { // Prepare data changes: - final DataObject dataBefore1 = mockDataObject("before", Ethernet.class); - final DataObject dataAfter1 = mockDataObject("after", Ethernet.class); - - final DataObject dataBefore2 = mockDataObject("before", Vxlan.class); - final DataObject dataAfter2 = mockDataObject("after", Vxlan.class); + final DataObject dataBefore = mockDataObject("before", Ethernet.class); + final DataObject dataAfter = mockDataObject("after", Ethernet.class); - final DataObject dataBefore3 = mockDataObject("before", L2.class); - final DataObject dataAfter3 = mockDataObject("after", L2.class); + final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class); - // reject third applied change - final WriterRegistry.Revert revert = mock(WriterRegistry.Revert.class); - doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(L2.class), new RuntimeException(), - revert)).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class)); + // Fail on update: + final VppException failedOnUpdateException = new VppException("update failed"); + doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter, + failedOnUpdateException)).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class)); // Prepare modification: final DataTreeCandidateNode rootNode = mockRootNode(); // data before: - final ContainerNode nodeBefore = mockContainerNode(dataBefore1, dataBefore2, dataBefore3); + final ContainerNode nodeBefore = mockContainerNode(dataBefore); when(rootNode.getDataBefore()).thenReturn(Optional.>fromNullable(nodeBefore)); // data after: - final ContainerNode nodeAfter = mockContainerNode(dataAfter1, dataAfter2, dataAfter3); + final ContainerNode nodeAfter = mockContainerNode(dataAfter); when(rootNode.getDataAfter()).thenReturn(Optional.>fromNullable(nodeAfter)); // Run the test try { proxy.commit(modification); - } catch (DataValidationFailedException | VppException e) { + } catch (WriterRegistry.BulkUpdateException e) { verify(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class)); - verify(revert).revert(); + verify(reverter).revert(); + assertEquals(failedOnUpdateException, e.getCause()); return; } - fail("DataValidationFailedException was expected"); + fail("WriterRegistry.BulkUpdateException was expected"); } @Test public void testCommitUndoFailed() throws Exception { // Prepare data changes: - final DataObject dataBefore1 = mockDataObject("before", Ethernet.class); - final DataObject dataAfter1 = mockDataObject("after", Ethernet.class); + final DataObject dataBefore = mockDataObject("before", Ethernet.class); + final DataObject dataAfter = mockDataObject("after", Ethernet.class); - final DataObject dataBefore2 = mockDataObject("before", Vxlan.class); - final DataObject dataAfter2 = mockDataObject("after", Vxlan.class); + final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class); - final DataObject dataBefore3 = mockDataObject("before", L2.class); - final DataObject dataAfter3 = mockDataObject("after", L2.class); + // Fail on update: + doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter, + new VppException("update failed"))).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class)); - // reject third applied change - final WriterRegistry.Revert revert = mock(WriterRegistry.Revert.class); - doThrow(new RuntimeException("revert failed")).when(revert).revert(); - doThrow(new WriterRegistry.BulkUpdateException(InstanceIdentifier.create(L2.class), new RuntimeException(), - revert)).when(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class)); + // Fail on revert: + final VppException failedOnRevertException = new VppException("update failed"); + final WriterRegistry.Reverter.RevertFailedException revertFailedException = + new WriterRegistry.Reverter.RevertFailedException(Collections.>emptyList(), + failedOnRevertException); + doThrow(revertFailedException).when(reverter).revert(); // Prepare modification: final DataTreeCandidateNode rootNode = mockRootNode(); // data before: - final ContainerNode nodeBefore = mockContainerNode(dataBefore1, dataBefore2, dataBefore3); + final ContainerNode nodeBefore = mockContainerNode(dataBefore); when(rootNode.getDataBefore()).thenReturn(Optional.>fromNullable(nodeBefore)); // data after: - final ContainerNode nodeAfter = mockContainerNode(dataAfter1, dataAfter2, dataAfter3); + final ContainerNode nodeAfter = mockContainerNode(dataAfter); when(rootNode.getDataAfter()).thenReturn(Optional.>fromNullable(nodeAfter)); // Run the test try { proxy.commit(modification); - } catch (DataValidationFailedException | VppException e) { - // FIXME the behavior with successful and failed revert is the same from outside world + } catch (WriterRegistry.Reverter.RevertFailedException e) { verify(vppWriter).update(anyMap(), anyMap(), any(WriteContext.class)); - verify(revert).revert(); + verify(reverter).revert(); + assertEquals(failedOnRevertException, e.getCause()); return; } - fail("DataValidationFailedException was expected"); + fail("RevertFailedException was expected"); } private DataTreeCandidateNode mockRootNode() { @@ -256,9 +253,9 @@ public class VPPConfigDataTreeTest { when(child.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class)); list.add(child); - final Map.Entry entry = mock(Map.Entry.class); + final Map.Entry entry = mock(Map.Entry.class); final Class implementedInterface = - (Class) modification.getImplementedInterface(); + (Class) modification.getImplementedInterface(); final InstanceIdentifier id = InstanceIdentifier.create(implementedInterface); doReturn(id).when(entry).getKey(); @@ -267,5 +264,4 @@ public class VPPConfigDataTreeTest { } return node; } - } diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java index f4b2faa3e..8939f8f52 100644 --- a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java +++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTreeTest.java @@ -16,16 +16,22 @@ package io.fd.honeycomb.v3po.impl.data; -import static junit.framework.Assert.assertEquals; -import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import com.google.common.base.Optional; +import com.google.common.collect.Iterables; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; import com.google.common.util.concurrent.CheckedFuture; import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry; import java.util.Map; @@ -34,10 +40,12 @@ import org.junit.Test; import org.mockito.Mock; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; @@ -65,16 +73,16 @@ public class VppOperationalDataTreeTest { public void setUp() { initMocks(this); operationalData = new VppOperationalDataTree(serializer, globalContext, reader); + doReturn(schemaNode).when(globalContext).getDataChildByName(any(QName.class)); } @Test - public void testRead() throws Exception { + public void testReadNode() throws Exception { final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class); final YangInstanceIdentifier.PathArgument pArg = mock(YangInstanceIdentifier.PathArgument.class); doReturn(pArg).when(yangId).getLastPathArgument(); doReturn(QName.create("namespace", "2012-12-12", "local")).when(pArg).getNodeType(); - doReturn(schemaNode).when(globalContext).getDataChildByName(any(QName.class)); doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId); final DataObject dataObject = mock(DataObject.class); @@ -91,6 +99,69 @@ public class VppOperationalDataTreeTest { final Optional> result = future.get(); assertTrue(result.isPresent()); assertEquals(expectedValue, result.get()); + } + + @Test + public void testReadNonExistingNode() throws Exception { + final YangInstanceIdentifier yangId = mock(YangInstanceIdentifier.class); + doReturn(id).when(serializer).fromYangInstanceIdentifier(yangId); + doReturn(Optional.absent()).when(reader).read(id); + + final CheckedFuture>, ReadFailedException> future = operationalData.read(yangId); + + verify(serializer).fromYangInstanceIdentifier(yangId); + verify(reader).read(id); + final Optional> result = future.get(); + assertFalse(result.isPresent()); + } + + @Test + public void testReadFailed() throws Exception{ + doThrow(io.fd.honeycomb.v3po.impl.trans.ReadFailedException.class).when(reader).readAll(); + + final CheckedFuture>, ReadFailedException> future = + operationalData.read( YangInstanceIdentifier.EMPTY); + + try { + future.checkedGet(); + } catch (ReadFailedException e) { + assertTrue(e.getCause() instanceof io.fd.honeycomb.v3po.impl.trans.ReadFailedException); + return; + } + fail("ReadFailedException was expected"); + } + + @Test + public void testReadRootWithOneNonListElement() throws Exception { + // Prepare data + final InstanceIdentifier vppStateII = InstanceIdentifier.create(VppState.class); + final VppState vppState = mock(VppState.class); + Multimap, DataObject> dataObjects = LinkedListMultimap.create(); + dataObjects.put(vppStateII, vppState); + doReturn(dataObjects).when(reader).readAll(); + + // Init serializer + final YangInstanceIdentifier vppYangId = YangInstanceIdentifier.builder().node(VppState.QNAME).build(); + when(serializer.toYangInstanceIdentifier(vppStateII)).thenReturn(vppYangId); + when(serializer.toNormalizedNode(vppStateII, vppState)).thenReturn(entry); + final DataContainerChild vppStateContainer = mock(DataContainerChild.class); + doReturn(vppStateContainer).when(entry).getValue(); + doReturn(vppYangId.getLastPathArgument()).when(vppStateContainer).getIdentifier(); + + // Read root + final CheckedFuture>, ReadFailedException> future = + operationalData.read(YangInstanceIdentifier.EMPTY); + + verify(reader).readAll(); + verify(serializer).toYangInstanceIdentifier(vppStateII); + verify(serializer).toNormalizedNode(vppStateII, vppState); + + // Check the result is an ContainerNode with only one child + final Optional> result = future.get(); + assertTrue(result.isPresent()); + final ContainerNode rootNode = (ContainerNode) result.get(); + assertEquals(SchemaContext.NAME, rootNode.getIdentifier().getNodeType()); + assertEquals(vppStateContainer, Iterables.getOnlyElement(rootNode.getValue())); } } \ No newline at end of file diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java new file mode 100644 index 000000000..b815434a8 --- /dev/null +++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedExceptionTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.impl.trans; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.BridgeDomain; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.state.bridge.domains.bridge.domain.Interface; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class ReadFailedExceptionTest { + + @Test + public void testInstantiation() { + final InstanceIdentifier id = InstanceIdentifier.create(BridgeDomain.class); + ReadFailedException e = new ReadFailedException(id); + assertEquals(id, e.getFailedId()); + assertNull(e.getCause()); + assertTrue(e.getMessage().contains(id.toString())); + } + + @Test + public void testInstantiationWithCause() { + final InstanceIdentifier id = InstanceIdentifier.create(Interface.class); + final RuntimeException cause = new RuntimeException(); + ReadFailedException e = new ReadFailedException(id, cause); + assertEquals(id, e.getFailedId()); + assertEquals(cause, e.getCause()); + assertTrue(e.getMessage().contains(id.toString())); + } + + @Test(expected = NullPointerException.class) + public void testInstantiationFailed() { + new ReadFailedException(null); + } +} \ No newline at end of file diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java new file mode 100644 index 000000000..26f63f40f --- /dev/null +++ b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistryTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.impl.trans.w.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import io.fd.honeycomb.v3po.impl.trans.VppException; +import io.fd.honeycomb.v3po.impl.trans.w.VppWriter; +import io.fd.honeycomb.v3po.impl.trans.w.WriteContext; +import io.fd.honeycomb.v3po.impl.trans.w.WriterRegistry; +import io.fd.honeycomb.v3po.impl.trans.w.impl.CompositeRootVppWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class DelegatingWriterRegistryTest { + + private final InstanceIdentifier vppId; + private final InstanceIdentifier vppStateId; + private final InstanceIdentifier interfaceId; + + private WriteContext ctx; + private CompositeRootVppWriter vppWriter; + private CompositeRootVppWriter vppStateWriter; + private CompositeRootVppWriter interfacesWriter; + + private DelegatingWriterRegistry registry; + + public DelegatingWriterRegistryTest() { + vppId = InstanceIdentifier.create(Vpp.class); + vppStateId = InstanceIdentifier.create(VppState.class); + interfaceId = InstanceIdentifier.create(Interfaces.class); + } + + @SuppressWarnings("unchecked") + private CompositeRootVppWriter mockWriter(Class clazz) { + final CompositeRootVppWriter mock = (CompositeRootVppWriter) Mockito.mock(CompositeRootVppWriter.class); + doReturn(InstanceIdentifier.create(clazz)).when(mock).getManagedDataObjectType(); + return mock; + } + + private DataObject mockDataObject(final String name, final Class classToMock) { + final DataObject dataBefore = mock(classToMock, name); + doReturn(classToMock).when(dataBefore).getImplementedInterface(); + return dataBefore; + } + + @SuppressWarnings("unchecked") + private static Map, DataObject> asMap(DataObject... objects) { + final Map, DataObject> map = new HashMap<>(); + for (DataObject object : objects) { + final Class implementedInterface = + (Class) object.getImplementedInterface(); + final InstanceIdentifier id = InstanceIdentifier.create(implementedInterface); + map.put(id, object); + } + return map; + } + + @Before + public void setUp() { + ctx = mock(WriteContext.class); + vppWriter = mockWriter(Vpp.class); + vppStateWriter = mockWriter(VppState.class); + interfacesWriter = mockWriter(Interfaces.class); + + final List> writers = new ArrayList<>(); + writers.add(vppWriter); + writers.add(vppStateWriter); + writers.add(interfacesWriter); + + registry = new DelegatingWriterRegistry(writers); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetManagedDataObjectType() { + registry.getManagedDataObjectType(); + } + + @Test + public void testBulkUpdateRevert() throws Exception { + // Prepare data changes: + final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class); + final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class); + + final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class); + final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class); + + // Fail on update + doThrow(new VppException("vpp failed")).when(vppStateWriter) + .update(vppStateId, dataBefore2, dataAfter2, ctx); + + // Run the test + try { + registry.update(asMap(dataBefore1, dataBefore2), asMap(dataAfter1, dataAfter2), ctx); + } catch (WriterRegistry.BulkUpdateException e) { + // Check second update failed + assertEquals(vppStateId, e.getFailedId()); + verify(vppWriter).update(vppId, dataBefore1, dataAfter1, ctx); + verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx); + + // Try to revert changes + e.revertChanges(); + + // Check revert was successful + verify(vppWriter).update(vppId, dataAfter1, dataBefore1, ctx); + verify(vppStateWriter, never()).update(vppStateId, dataAfter2, dataBefore2, ctx); + + return; + } + fail("BulkUpdateException expected"); + } + + @Test + public void testBulkUpdateRevertFail() throws Exception { + // Prepare data changes: + final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class); + final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class); + + final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class); + final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class); + + final DataObject dataBefore3 = mockDataObject("Interfaces before", Interfaces.class); + final DataObject dataAfter3 = mockDataObject("Interfaces after", Interfaces.class); + + // Fail on the third update + doThrow(new VppException("vpp failed")).when(interfacesWriter) + .update(interfaceId, dataBefore3, dataAfter3, ctx); + + // Fail on the second revert + doThrow(new VppException("vpp failed again")).when(vppWriter) + .update(vppId, dataAfter1, dataBefore1, ctx); + + // Run the test + try { + registry.update(asMap(dataBefore1, dataBefore2, dataBefore3), asMap(dataAfter1, dataAfter2, dataAfter3), ctx); + } catch (WriterRegistry.BulkUpdateException e) { + // Check third update failed + assertEquals(interfaceId, e.getFailedId()); + verify(vppWriter).update(vppId, dataBefore1, dataAfter1, ctx); + verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx); + verify(interfacesWriter).update(interfaceId, dataBefore3, dataAfter3, ctx); + + // Try to revert changes + try { + e.revertChanges(); + } catch (WriterRegistry.Reverter.RevertFailedException e2) { + // Check second revert failed + assertEquals(Collections.singletonList(vppId), e2.getNotRevertedChanges()); + verify(vppWriter).update(vppId, dataAfter1, dataBefore1, ctx); + verify(vppStateWriter).update(vppStateId, dataAfter2, dataBefore2, ctx); + verify(interfacesWriter, never()).update(interfaceId, dataAfter3, dataBefore3, ctx); + return; + } + fail("WriterRegistry.Revert.RevertFailedException expected"); + } + fail("BulkUpdateException expected"); + } +} \ No newline at end of file -- cgit 1.2.3-korg