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/VppConfigDataTree.java | 12 +- .../v3po/impl/data/VppOperationalDataTree.java | 66 ++++--- .../v3po/impl/data/VppReaderRegistry.java | 7 +- .../v3po/impl/data/VppWriterRegistry.java | 2 +- .../v3po/impl/trans/ReadFailedException.java | 60 +++++++ .../v3po/impl/trans/VppApiInvocationException.java | 2 +- .../fd/honeycomb/v3po/impl/trans/VppException.java | 10 +- .../v3po/impl/trans/r/ChildVppReader.java | 4 +- .../honeycomb/v3po/impl/trans/r/ListVppReader.java | 4 +- .../v3po/impl/trans/r/ReaderRegistry.java | 9 +- .../fd/honeycomb/v3po/impl/trans/r/VppReader.java | 5 +- .../trans/r/impl/AbstractCompositeVppReader.java | 13 +- .../impl/trans/r/impl/CompositeChildVppReader.java | 6 +- .../impl/trans/r/impl/CompositeListVppReader.java | 30 ++-- .../impl/trans/r/impl/CompositeRootVppReader.java | 4 +- .../trans/r/impl/spi/RootVppReaderCustomizer.java | 15 +- .../trans/r/util/DelegatingReaderRegistry.java | 23 +-- .../fd/honeycomb/v3po/impl/trans/w/VppWriter.java | 10 +- .../v3po/impl/trans/w/WriterRegistry.java | 93 ++++++++-- .../trans/w/util/DelegatingWriterRegistry.java | 64 +++---- .../v3po/impl/data/VPPConfigDataTreeTest.java | 84 +++++---- .../v3po/impl/data/VppOperationalDataTreeTest.java | 79 ++++++++- .../v3po/impl/trans/ReadFailedExceptionTest.java | 53 ++++++ .../trans/w/util/DelegatingWriterRegistryTest.java | 189 +++++++++++++++++++++ 24 files changed, 663 insertions(+), 181 deletions(-) create mode 100644 v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java 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') diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java index f14bec3aa..9f34fcbd1 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppConfigDataTree.java @@ -118,15 +118,13 @@ public final class VppConfigDataTree implements VppDataTree { try { e.revertChanges(); LOG.info("Changes successfully reverted"); - } catch (VppException | RuntimeException e2) { - LOG.error("Failed to revert successful changes", e2); + } catch (WriterRegistry.Reverter.RevertFailedException revertFailedException) { + // fail with failed revert + LOG.error("Failed to revert successful changes", revertFailedException); + throw revertFailedException; } - // rethrow as we can't do anything more about it - // FIXME we need to throw a different kind of exception here to differentiate between: - // fail with success revert - // fail with failed revert (this one needs to contain IDs of changes that were not reverted) - throw e; + throw e; // fail with success revert } catch (VppException e) { LOG.error("Error while processing data change (before={}, after={})", nodesBefore, nodesAfter, e); throw e; diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java index 3fddd108c..d0acd05a8 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppOperationalDataTree.java @@ -26,12 +26,12 @@ import com.google.common.collect.Collections2; import com.google.common.collect.Multimap; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry; import java.util.Collection; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @@ -52,7 +52,6 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * ReadableVppDataTree implementation for operational data. */ @@ -79,34 +78,44 @@ public final class VppOperationalDataTree implements ReadableVppDataTree { } @Override - public CheckedFuture>, ReadFailedException> read( + public CheckedFuture>, + org.opendaylight.controller.md.sal.common.api.data.ReadFailedException> read( @Nonnull final YangInstanceIdentifier yangInstanceIdentifier) { - if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) { - LOG.debug("VppOperationalDataProxy.read(), yangInstanceIdentifier=ROOT"); - return Futures.immediateCheckedFuture(Optional.>of(readRoot())); + try { + if (checkNotNull(yangInstanceIdentifier).equals(YangInstanceIdentifier.EMPTY)) { + return Futures.immediateCheckedFuture(readRoot()); + } else { + return Futures.immediateCheckedFuture(readNode(yangInstanceIdentifier)); + } + } catch (ReadFailedException e) { + return Futures.immediateFailedCheckedFuture( + new org.opendaylight.controller.md.sal.common.api.data.ReadFailedException( + "Failed to read VPP data", e)); } + } - LOG.debug("VppOperationalDataProxy.read(), yangInstanceIdentifier={}", yangInstanceIdentifier); + private Optional> readNode(final YangInstanceIdentifier yangInstanceIdentifier) + throws ReadFailedException { + LOG.debug("VppOperationalDataTree.readNode(), yangInstanceIdentifier={}", yangInstanceIdentifier); final InstanceIdentifier path = serializer.fromYangInstanceIdentifier(yangInstanceIdentifier); checkNotNull(path, "Invalid instance identifier %s. Cannot create BA equivalent.", yangInstanceIdentifier); - LOG.debug("VppOperationalDataProxy.read(), path={}", path); + LOG.debug("VppOperationalDataTree.readNode(), path={}", path); - final Optional dataObject = readerRegistry.read(path); + final Optional dataObject; + dataObject = readerRegistry.read(path); if (dataObject.isPresent()) { final NormalizedNode value = toNormalizedNodeFunction(path).apply(dataObject.get()); - return Futures.immediateCheckedFuture(Optional.>fromNullable(value)); + return Optional.>fromNullable(value); + } else { + return Optional.absent(); } - - return Futures.immediateCheckedFuture(Optional.>absent()); } - private DataSchemaNode getSchemaNode(final @Nonnull YangInstanceIdentifier yangInstanceIdentifier) { - return globalContext.getDataChildByName(yangInstanceIdentifier.getLastPathArgument().getNodeType()); - } + private Optional> readRoot() throws ReadFailedException { + LOG.debug("VppOperationalDataTree.readRoot()"); - private NormalizedNode readRoot() { final DataContainerNodeAttrBuilder dataNodeBuilder = Builders.containerBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME)); @@ -121,16 +130,17 @@ public final class VppOperationalDataTree implements ReadableVppDataTree { dataNodeBuilder.withChild((DataContainerChild) node); } - return dataNodeBuilder.build(); + return Optional.>of(dataNodeBuilder.build()); } private NormalizedNode wrapDataObjects(final YangInstanceIdentifier yangInstanceIdentifier, - final InstanceIdentifier instanceIdentifier, - final Collection dataObjects) { + final InstanceIdentifier instanceIdentifier, + final Collection dataObjects) { final Collection> normalizedRootElements = Collections2 .transform(dataObjects, toNormalizedNodeFunction(instanceIdentifier)); - final DataSchemaNode schemaNode = getSchemaNode(yangInstanceIdentifier); + final DataSchemaNode schemaNode = + globalContext.getDataChildByName(yangInstanceIdentifier.getLastPathArgument().getNodeType()); if (schemaNode instanceof ListSchemaNode) { // In case of a list, wrap all the values in a Mixin parent node final ListSchemaNode listSchema = (ListSchemaNode) schemaNode; @@ -142,19 +152,19 @@ public final class VppOperationalDataTree implements ReadableVppDataTree { } private static DataContainerChild wrapListIntoMixinNode( - final Collection> normalizedRootElements, final ListSchemaNode listSchema) { + final Collection> normalizedRootElements, final ListSchemaNode listSchema) { if (listSchema.getKeyDefinition().isEmpty()) { final CollectionNodeBuilder listBuilder = - Builders.unkeyedListBuilder(); + Builders.unkeyedListBuilder(); for (NormalizedNode normalizedRootElement : normalizedRootElements) { listBuilder.withChild((UnkeyedListEntryNode) normalizedRootElement); } return listBuilder.build(); } else { final CollectionNodeBuilder listBuilder = - listSchema.isUserOrdered() - ? Builders.orderedMapBuilder() - : Builders.mapBuilder(); + listSchema.isUserOrdered() + ? Builders.orderedMapBuilder() + : Builders.mapBuilder(); for (NormalizedNode normalizedRootElement : normalizedRootElements) { listBuilder.withChild((MapEntryNode) normalizedRootElement); @@ -168,11 +178,11 @@ public final class VppOperationalDataTree implements ReadableVppDataTree { return new Function>() { @Override public NormalizedNode apply(@Nullable final DataObject dataObject) { - LOG.trace("VppOperationalDataProxy.toNormalizedNode(), path={}, dataObject={}", path, dataObject); + LOG.trace("VppOperationalDataTree.toNormalizedNode(), path={}, dataObject={}", path, dataObject); final Map.Entry> entry = - serializer.toNormalizedNode(path, dataObject); + serializer.toNormalizedNode(path, dataObject); - LOG.trace("VppOperationalDataProxy.toNormalizedNode(), normalizedNodeEntry={}", entry); + LOG.trace("VppOperationalDataTree.toNormalizedNode(), normalizedNodeEntry={}", entry); return entry.getValue(); } }; diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppReaderRegistry.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppReaderRegistry.java index df7b6568c..72d17b7e2 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppReaderRegistry.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppReaderRegistry.java @@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.data; import com.google.common.base.Optional; import com.google.common.collect.Multimap; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader; import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry; import io.fd.honeycomb.v3po.impl.trans.r.VppReader; @@ -95,13 +96,15 @@ public class VppReaderRegistry implements ReaderRegistry { @Nonnull @Override - public Multimap, ? extends DataObject> readAll() { + public Multimap, ? extends DataObject> readAll() + throws io.fd.honeycomb.v3po.impl.trans.ReadFailedException { return reader.readAll(); } @Nonnull @Override - public Optional read(@Nonnull final InstanceIdentifier id) { + public Optional read(@Nonnull final InstanceIdentifier id) + throws ReadFailedException { return reader.read(id); } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppWriterRegistry.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppWriterRegistry.java index 83186c2de..defcca47e 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppWriterRegistry.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppWriterRegistry.java @@ -98,7 +98,7 @@ public class VppWriterRegistry implements WriterRegistry { public void update(@Nonnull final Map, DataObject> dataBefore, @Nonnull final Map, DataObject> dataAfter, @Nonnull final WriteContext ctx) - throws VppException, BulkUpdateException { + throws VppException { writer.update(dataBefore, dataAfter, ctx); } } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java new file mode 100644 index 000000000..4da8b0e73 --- /dev/null +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/ReadFailedException.java @@ -0,0 +1,60 @@ +/* + * 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 com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Thrown when Vpp reader or customizer is not able to read data for the given id. + */ +public class ReadFailedException extends VppException { + + private final InstanceIdentifier failedId; + + /** + * Constructs an ReadFailedException given data id and exception cause. + * + * @param failedId instance identifier of the data object that could not be read + * @param cause the cause of read failure + */ + public ReadFailedException(@Nonnull final InstanceIdentifier failedId, final Throwable cause) { + super("Failed to read " + failedId, cause); + this.failedId = checkNotNull(failedId, "failedId should not be null"); + } + + /** + * Constructs an ReadFailedException given data id. + * + * @param failedId instance identifier of the data object that could not be read + */ + public ReadFailedException(@Nonnull final InstanceIdentifier failedId) { + this(failedId, null); + } + + /** + * Returns id of the data object that could not be read. + * + * @return data object instance identifier + */ + @Nonnull + public InstanceIdentifier getFailedId() { + return failedId; + } +} diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppApiInvocationException.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppApiInvocationException.java index 0bb7c2b19..042c627ef 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppApiInvocationException.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppApiInvocationException.java @@ -21,7 +21,7 @@ import com.google.common.base.Preconditions; import javax.annotation.Nonnull; /** - * Throws when Vpp jAPI method invocation failed. + * Thrown when Vpp jAPI method invocation failed. */ @Beta public class VppApiInvocationException extends VppException { diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppException.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppException.java index aa18ae705..aadeaa996 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppException.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/VppException.java @@ -19,7 +19,7 @@ package io.fd.honeycomb.v3po.impl.trans; import com.google.common.annotations.Beta; /** - * Base exception for Vpp writers + * Base exception for Vpp translation layer */ @Beta public class VppException extends Exception { @@ -28,11 +28,11 @@ public class VppException extends Exception { super(s); } - public VppException(final String s, final Throwable throwable) { - super(s, throwable); + public VppException(final String s, final Throwable cause) { + super(s, cause); } - public VppException(final Throwable throwable) { - super(throwable); + public VppException(final Throwable cause) { + super(cause); } } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ChildVppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ChildVppReader.java index 7f4d6fc4b..aad3080d4 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ChildVppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ChildVppReader.java @@ -17,6 +17,7 @@ package io.fd.honeycomb.v3po.impl.trans.r; import com.google.common.annotations.Beta; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import javax.annotation.Nonnull; import org.opendaylight.yangtools.concepts.Builder; import org.opendaylight.yangtools.yang.binding.DataObject; @@ -38,9 +39,10 @@ public interface ChildVppReader extends VppReader { * determine the exact position within more complex subtrees. * @param parentBuilder Builder of parent DataObject. Objects read on this level (if any) must be placed into the * parent builder. + * @throws ReadFailedException if read was unsuccessful */ void read(@Nonnull final InstanceIdentifier id, - @Nonnull final Builder parentBuilder); + @Nonnull final Builder parentBuilder) throws ReadFailedException; } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ListVppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ListVppReader.java index 8a7f29cb3..ce392c6bc 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ListVppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ListVppReader.java @@ -17,6 +17,7 @@ package io.fd.honeycomb.v3po.impl.trans.r; import com.google.common.annotations.Beta; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import java.util.List; import javax.annotation.Nonnull; import org.opendaylight.yangtools.yang.binding.DataObject; @@ -38,7 +39,8 @@ public interface ListVppReader, K extends * @param id Wildcarded identifier of list managed by this reader * * @return List of all entries in this list + * @throws ReadFailedException if read was unsuccessful */ @Nonnull - List readList(@Nonnull final InstanceIdentifier id); + List readList(@Nonnull final InstanceIdentifier id) throws ReadFailedException; } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ReaderRegistry.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ReaderRegistry.java index 8c592a699..6a9937679 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ReaderRegistry.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/ReaderRegistry.java @@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.trans.r; import com.google.common.annotations.Beta; import com.google.common.collect.Multimap; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import javax.annotation.Nonnull; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @@ -29,11 +30,13 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; public interface ReaderRegistry extends VppReader { /** - * Performs read on all registered root readers and merges the results into a Multimap. - * Keys represent identifiers for root DataObjects from the data tree modeled by YANG. + * Performs read on all registered root readers and merges the results into a Multimap. Keys represent identifiers + * for root DataObjects from the data tree modeled by YANG. * * @return multimap that preserves deterministic iteration order across non-distinct key values + * @throws ReadFailedException if read was unsuccessful */ @Nonnull - Multimap, ? extends DataObject> readAll(); + Multimap, ? extends DataObject> readAll() + throws ReadFailedException; } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/VppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/VppReader.java index 1cc76a3a4..02189e42d 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/VppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/VppReader.java @@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.trans.r; import com.google.common.annotations.Beta; import com.google.common.base.Optional; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.SubtreeManager; import javax.annotation.Nonnull; import org.opendaylight.yangtools.yang.binding.DataObject; @@ -42,8 +43,10 @@ public interface VppReader extends SubtreeManager { * identifiers pointing below node managed by this reader, it's reader's responsibility to filter out the * right node or to delegate the read to a child reader. * @return List of DataObjects identified by id. If the ID points to a single node, it will be wrapped in a list + * @throws ReadFailedException if read was unsuccessful */ @Nonnull - Optional read(@Nonnull final InstanceIdentifier id); + Optional read(@Nonnull final InstanceIdentifier id) throws + ReadFailedException; } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/AbstractCompositeVppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/AbstractCompositeVppReader.java index 61f318b35..061cfc9f3 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/AbstractCompositeVppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/AbstractCompositeVppReader.java @@ -22,6 +22,7 @@ import com.google.common.annotations.Beta; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader; import io.fd.honeycomb.v3po.impl.trans.r.VppReader; import io.fd.honeycomb.v3po.impl.trans.util.ReflectionUtils; @@ -68,7 +69,8 @@ abstract class AbstractCompositeVppReader readCurrent(final InstanceIdentifier id) { + protected Optional readCurrent(final InstanceIdentifier id) throws + ReadFailedException { LOG.debug("{}: Reading current: {}", this, id); final B builder = getBuilder(id); // Cache empty value to determine if anything has changed later TODO cache in a field @@ -101,7 +103,8 @@ abstract class AbstractCompositeVppReader read(@Nonnull final InstanceIdentifier id) { + public Optional read(@Nonnull final InstanceIdentifier id) + throws ReadFailedException { LOG.trace("{}: Reading : {}", this, id); if (id.getTargetType().equals(getManagedDataObjectType().getTargetType())) { return readCurrent((InstanceIdentifier) id); @@ -110,7 +113,8 @@ abstract class AbstractCompositeVppReader readSubtree(final InstanceIdentifier id) { + private Optional readSubtree(final InstanceIdentifier id) + throws ReadFailedException { LOG.debug("{}: Reading subtree: {}", this, id); final Class next = VppRWUtils.getNextId(id, getManagedDataObjectType()).getType(); final ChildVppReader> vppReader = childReaders.get(next); @@ -139,7 +143,8 @@ abstract class AbstractCompositeVppReader id, B builder); + protected abstract void readCurrentAttributes(final InstanceIdentifier id, B builder) throws + ReadFailedException; /** * Return new instance of a builder object for current node diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeChildVppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeChildVppReader.java index a64a72bc4..f18a5b38a 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeChildVppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeChildVppReader.java @@ -18,6 +18,7 @@ package io.fd.honeycomb.v3po.impl.trans.r.impl; import com.google.common.annotations.Beta; import com.google.common.base.Optional; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader; import io.fd.honeycomb.v3po.impl.trans.r.impl.spi.ChildVppReaderCustomizer; import io.fd.honeycomb.v3po.impl.trans.util.VppRWUtils; @@ -78,7 +79,7 @@ public final class CompositeChildVppReader parentId, - @Nonnull final Builder parentBuilder) { + @Nonnull final Builder parentBuilder) throws ReadFailedException { final Optional read = readCurrent(VppRWUtils.appendTypeToId(parentId, getManagedDataObjectType())); if(read.isPresent()) { @@ -87,7 +88,8 @@ public final class CompositeChildVppReader id, @Nonnull final B builder) { + protected void readCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final B builder) + throws ReadFailedException { customizer.readCurrentAttributes(id, builder); } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeListVppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeListVppReader.java index 7e3d8452e..a9ca3e788 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeListVppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeListVppReader.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument; import com.google.common.annotations.Beta; import com.google.common.base.Optional; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader; import io.fd.honeycomb.v3po.impl.trans.r.ListVppReader; import io.fd.honeycomb.v3po.impl.trans.r.impl.spi.ListVppReaderCustomizer; @@ -39,17 +40,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Composite implementation of {@link ChildVppReader} able to place the read result into - * parent builder object intended for list node type. + * Composite implementation of {@link ChildVppReader} able to place the read result into parent builder object intended + * for list node type. * - * This reader checks if the IDs are wildcarded in which case it performs read of all - * list entries. In case the ID has a key, it reads only the specified value. + * This reader checks if the IDs are wildcarded in which case it performs read of all list entries. In case the ID has a + * key, it reads only the specified value. */ @Beta @ThreadSafe public final class CompositeListVppReader, K extends Identifier, B extends Builder> - extends AbstractCompositeVppReader implements ChildVppReader, ListVppReader -{ + extends AbstractCompositeVppReader implements ChildVppReader, ListVppReader { private static final Logger LOG = LoggerFactory.getLogger(CompositeListVppReader.class); @@ -59,10 +59,9 @@ public final class CompositeListVppReader * Create new {@link CompositeListVppReader} * * @param managedDataObjectType Class object for managed data type. Must come from a list node type. - * @param childReaders Child nodes(container, list) readers - * @param augReaders Child augmentations readers - * @param customizer Customizer instance to customize this generic reader - * + * @param childReaders Child nodes(container, list) readers + * @param augReaders Child augmentations readers + * @param customizer Customizer instance to customize this generic reader */ public CompositeListVppReader(@Nonnull final Class managedDataObjectType, @Nonnull final List>> childReaders, @@ -87,12 +86,12 @@ public final class CompositeListVppReader public CompositeListVppReader(@Nonnull final Class managedDataObjectType, @Nonnull final ListVppReaderCustomizer customizer) { this(managedDataObjectType, VppRWUtils.emptyChildReaderList(), VppRWUtils.emptyAugReaderList(), - customizer); + customizer); } @Override public void read(@Nonnull final InstanceIdentifier id, - @Nonnull final Builder parentBuilder) { + @Nonnull final Builder parentBuilder) throws ReadFailedException { // Create ID pointing to current node final InstanceIdentifier currentId = VppRWUtils.appendTypeToId(id, getManagedDataObjectType()); // Read all, since current ID is definitely wildcarded @@ -102,7 +101,7 @@ public final class CompositeListVppReader @Override @Nonnull - public List readList(@Nonnull final InstanceIdentifier id) { + public List readList(@Nonnull final InstanceIdentifier id) throws ReadFailedException { LOG.trace("{}: Reading all list entries", this); final List allIds = customizer.getAllIds(id); LOG.debug("{}: Reading list entries for: {}", this, allIds); @@ -110,7 +109,7 @@ public final class CompositeListVppReader final ArrayList allEntries = new ArrayList<>(allIds.size()); for (K key : allIds) { final InstanceIdentifier.IdentifiableItem currentBdItem = - VppRWUtils.getCurrentIdItem(id, key); + VppRWUtils.getCurrentIdItem(id, key); final InstanceIdentifier keyedId = VppRWUtils.replaceLastInId(id, currentBdItem); final Optional read = readCurrent(keyedId); final DataObject singleItem = read.get(); @@ -121,7 +120,8 @@ public final class CompositeListVppReader } @Override - protected void readCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final B builder) { + protected void readCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final B builder) + throws ReadFailedException { customizer.readCurrentAttributes(id, builder); } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeRootVppReader.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeRootVppReader.java index c87600b26..d5d82e7dd 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeRootVppReader.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/CompositeRootVppReader.java @@ -17,6 +17,7 @@ package io.fd.honeycomb.v3po.impl.trans.r.impl; import com.google.common.annotations.Beta; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ChildVppReader; import io.fd.honeycomb.v3po.impl.trans.r.VppReader; import io.fd.honeycomb.v3po.impl.trans.r.impl.spi.RootVppReaderCustomizer; @@ -76,7 +77,8 @@ public final class CompositeRootVppReader id, @Nonnull final B builder) { + protected void readCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final B builder) + throws ReadFailedException { customizer.readCurrentAttributes(id, builder); } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/spi/RootVppReaderCustomizer.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/spi/RootVppReaderCustomizer.java index a09ed0488..299e94367 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/spi/RootVppReaderCustomizer.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/impl/spi/RootVppReaderCustomizer.java @@ -17,6 +17,7 @@ package io.fd.honeycomb.v3po.impl.trans.r.impl.spi; import com.google.common.annotations.Beta; +import io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import javax.annotation.Nonnull; import org.opendaylight.yangtools.concepts.Builder; import org.opendaylight.yangtools.yang.binding.DataObject; @@ -31,16 +32,20 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; @Beta public interface RootVppReaderCustomizer> { - // TODO add (un)checked, well defined exception here to indicate issues in the customizer - /** - * Create new builder that will be used to build read value + * Creates new builder that will be used to build read value. */ @Nonnull B getBuilder(@Nonnull final InstanceIdentifier id); + /** - * Add current data (identified by id) to the provided builder + * Adds current data (identified by id) to the provided builder. + * + * @param id id of current data object + * @param builder builder for creating read value + * @throws ReadFailedException if read was unsuccessful */ - void readCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final B builder); + void readCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final B builder) throws + ReadFailedException; } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/util/DelegatingReaderRegistry.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/util/DelegatingReaderRegistry.java index 0777425b2..20524073e 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/util/DelegatingReaderRegistry.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/r/util/DelegatingReaderRegistry.java @@ -22,6 +22,7 @@ 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 io.fd.honeycomb.v3po.impl.trans.ReadFailedException; import io.fd.honeycomb.v3po.impl.trans.r.ListVppReader; import io.fd.honeycomb.v3po.impl.trans.r.ReaderRegistry; import io.fd.honeycomb.v3po.impl.trans.r.VppReader; @@ -36,8 +37,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Simple reader registry able to perform and aggregated read (ROOT read) on top of all - * provided readers. Also able to delegate a specific read to one of the delegate readers. + * Simple reader registry able to perform and aggregated read (ROOT read) on top of all provided readers. Also able to + * delegate a specific read to one of the delegate readers. * * This could serve as a utility to hold & hide all available readers in upper layers. */ @@ -58,7 +59,8 @@ public final class DelegatingReaderRegistry implements ReaderRegistry { @Override @Nonnull - public Multimap, ? extends DataObject> readAll() { + public Multimap, ? extends DataObject> readAll() + throws ReadFailedException { LOG.debug("Reading from all delegates: {}", this); LOG.trace("Reading from all delegates: {}", rootReaders.values()); @@ -66,15 +68,15 @@ public final class DelegatingReaderRegistry implements ReaderRegistry { for (VppReader rootReader : rootReaders.values()) { LOG.debug("Reading from delegate: {}", rootReader); - if(rootReader instanceof ListVppReader) { + if (rootReader instanceof ListVppReader) { final List listEntries = - ((ListVppReader) rootReader).readList(rootReader.getManagedDataObjectType()); - if(!listEntries.isEmpty()) { + ((ListVppReader) rootReader).readList(rootReader.getManagedDataObjectType()); + if (!listEntries.isEmpty()) { objects.putAll(rootReader.getManagedDataObjectType(), listEntries); } } else { final Optional read = rootReader.read(rootReader.getManagedDataObjectType()); - if(read.isPresent()) { + if (read.isPresent()) { objects.putAll(rootReader.getManagedDataObjectType(), Collections.singletonList(read.get())); } } @@ -85,12 +87,13 @@ public final class DelegatingReaderRegistry implements ReaderRegistry { @Nonnull @Override - public Optional read(@Nonnull final InstanceIdentifier id) { + public Optional read(@Nonnull final InstanceIdentifier id) + throws ReadFailedException { final InstanceIdentifier.PathArgument first = checkNotNull( - Iterables.getFirst(id.getPathArguments(), null), "Empty id"); + Iterables.getFirst(id.getPathArguments(), null), "Empty id"); final VppReader vppReader = rootReaders.get(first.getType()); checkNotNull(vppReader, - "Unable to read %s. Missing reader. Current readers for: %s", id, rootReaders.keySet()); + "Unable to read %s. Missing reader. Current readers for: %s", id, rootReaders.keySet()); LOG.debug("Reading from delegate: {}", vppReader); return vppReader.read(id); } diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/VppWriter.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/VppWriter.java index f8a49a271..d338fafa2 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/VppWriter.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/VppWriter.java @@ -25,7 +25,8 @@ import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; /** - * Base VPP writer, responsible for translation between DataObjects and VPP APIs. Handling all update operations(create, update, delete) + * Base VPP writer, responsible for translation between DataObjects and VPP APIs. Handling all update operations(create, + * update, delete) * * @param Specific DataObject derived type, that is handled by this writer */ @@ -35,10 +36,11 @@ public interface VppWriter extends SubtreeManager { /** * Handle update operation. U from CRUD. * - * @param id Identifier(from root) of data being written + * @param id Identifier(from root) of data being written * @param dataBefore Old data - * @param dataAfter New, updated data - * @param ctx Write context enabling writer to get information about candidate data as well as current data + * @param dataAfter New, updated data + * @param ctx Write context enabling writer to get information about candidate data as well as current data + * @throws VppException if update failed */ void update(@Nonnull final InstanceIdentifier id, @Nullable final DataObject dataBefore, diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/WriterRegistry.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/WriterRegistry.java index 4b09ff29e..26c294b30 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/WriterRegistry.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/WriterRegistry.java @@ -16,8 +16,12 @@ package io.fd.honeycomb.v3po.impl.trans.w; +import static com.google.common.base.Preconditions.checkNotNull; + import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableList; import io.fd.honeycomb.v3po.impl.trans.VppException; +import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import org.opendaylight.yangtools.yang.binding.DataObject; @@ -33,23 +37,51 @@ public interface WriterRegistry extends VppWriter { * Performs bulk update * * @throws BulkUpdateException in case bulk update fails + * @throws VppException in case some other error occurs while processing update request */ void update(@Nonnull final Map, DataObject> dataBefore, @Nonnull final Map, DataObject> dataAfter, - @Nonnull final WriteContext ctx) throws VppException, BulkUpdateException; + @Nonnull final WriteContext ctx) throws VppException; + /** + * Thrown when bulk update failed. + */ @Beta - public class BulkUpdateException extends VppException { + class BulkUpdateException extends VppException { - private final Revert runnable; + private final Reverter reverter; + private final InstanceIdentifier failedId; // TODO change to VppDataModification + + /** + * Constructs an BulkUpdateException. + * + * @param failedId instance identifier of the data object that caused bulk update to fail. + * @param cause the cause of bulk update failure + */ + public BulkUpdateException(@Nonnull final InstanceIdentifier failedId, @Nonnull final Reverter reverter, + final Throwable cause) { + super("Bulk update failed at " + failedId, cause); + this.failedId = checkNotNull(failedId, "failedId should not be null"); + this.reverter = checkNotNull(reverter, "reverter should not be null"); + } - public BulkUpdateException(final InstanceIdentifier id, final RuntimeException e, final Revert runnable) { - super("Bulk edit failed at " + id, e); - this.runnable = runnable; + /** + * Reverts changes that were successfully applied during bulk update before failure occurred. + * + * @throws Reverter.RevertFailedException if revert fails + */ + public void revertChanges() throws Reverter.RevertFailedException { + reverter.revert(); } - public void revertChanges() throws VppException { - runnable.revert(); + /** + * Returns instance identifier of the data object that caused bulk update to fail. + * + * @return data object's instance identifier + */ + @Nonnull + public InstanceIdentifier getFailedId() { + return failedId; } } @@ -57,8 +89,47 @@ public interface WriterRegistry extends VppWriter { * Abstraction over revert mechanism in cast of a bulk update failure */ @Beta - public interface Revert { + interface Reverter { - public void revert() throws VppException; + /** + * Reverts changes that were successfully applied during bulk update before failure occurred. Changes are + * reverted in reverse order they were applied. + * + * @throws RevertFailedException if not all of applied changes were successfully reverted + */ + void revert() throws RevertFailedException; + + /** + * Thrown when some of the changes applied during bulk update were not reverted. + */ + @Beta + class RevertFailedException extends VppException { + + // TODO change to list of VppDataModifications to make debugging easier + private final List> notRevertedChanges; + + /** + * Constructs an RevertFailedException with the list of changes that were not reverted. + * + * @param notRevertedChanges list of changes that were not reverted + * @param cause the cause of revert failure + */ + public RevertFailedException(@Nonnull final List> notRevertedChanges, + final Throwable cause) { + super(cause); + checkNotNull(notRevertedChanges, "notRevertedChanges should not be null"); + this.notRevertedChanges = ImmutableList.copyOf(notRevertedChanges); + } + + /** + * Returns the list of changes that were not reverted. + * + * @return list of changes that were not reverted + */ + @Nonnull + public List> getNotRevertedChanges() { + return notRevertedChanges; + } + } } -} +} \ No newline at end of file diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistry.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistry.java index d20e69a8b..cc1188e17 100644 --- a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistry.java +++ b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/trans/w/util/DelegatingWriterRegistry.java @@ -29,8 +29,8 @@ import io.fd.honeycomb.v3po.impl.trans.util.VppRWUtils; 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 java.util.LinkedList; import java.util.List; -import java.util.ListIterator; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -40,8 +40,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Simple writer registry able to perform and aggregated read (ROOT write) on top of all - * provided writers. Also able to delegate a specific read to one of the delegate writers. + * Simple writer registry able to perform and aggregated read (ROOT write) on top of all provided writers. Also able to + * delegate a specific read to one of the delegate writers. * * This could serve as a utility to hold & hide all available writers in upper layers. */ @@ -50,12 +50,12 @@ public final class DelegatingWriterRegistry implements WriterRegistry { private static final Logger LOG = LoggerFactory.getLogger(DelegatingWriterRegistry.class); private static final Function, Class> ID_TO_CLASS = - new Function, Class>() { - @Override - public Class apply(final InstanceIdentifier input) { - return input.getTargetType(); - } - }; + new Function, Class>() { + @Override + public Class apply(final InstanceIdentifier input) { + return input.getTargetType(); + } + }; private final Map, VppWriter> rootWriters; @@ -84,10 +84,10 @@ public final class DelegatingWriterRegistry implements WriterRegistry { @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx) throws VppException { final InstanceIdentifier.PathArgument first = checkNotNull( - Iterables.getFirst(id.getPathArguments(), null), "Empty id"); + Iterables.getFirst(id.getPathArguments(), null), "Empty id"); final VppWriter vppWriter = rootWriters.get(first.getType()); checkNotNull(vppWriter, - "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet()); + "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet()); vppWriter.update(id, dataBefore, dataAfter, ctx); } @@ -101,7 +101,7 @@ public final class DelegatingWriterRegistry implements WriterRegistry { final List> processedNodes = Lists.newArrayList(); for (Map.Entry, VppWriter> rootWriterEntry : rootWriters - .entrySet()) { + .entrySet()) { final InstanceIdentifier id = rootWriterEntry.getValue().getManagedDataObjectType(); @@ -109,7 +109,7 @@ public final class DelegatingWriterRegistry implements WriterRegistry { final DataObject dataAfter = nodesAfter.get(id); // No change to current writer - if(dataBefore == null && dataAfter == null) { + if (dataBefore == null && dataAfter == null) { continue; } @@ -118,31 +118,32 @@ public final class DelegatingWriterRegistry implements WriterRegistry { try { update(id, dataBefore, dataAfter, ctx); processedNodes.add(id); - } catch (RuntimeException e) { - LOG.error("Error while processing data change of: {} (before={}, after={})", id, dataBefore, dataAfter, e); - throw new BulkUpdateException(id, e, new RevertImpl(this, processedNodes, nodesBefore, nodesAfter, ctx)); + } catch (Exception e) { + LOG.error("Error while processing data change of: {} (before={}, after={})", + id, dataBefore, dataAfter, e); + throw new BulkUpdateException( + id, new ReverterImpl(this, processedNodes, nodesBefore, nodesAfter, ctx), e); } } - } private void checkAllWritersPresent(final @Nonnull Map, DataObject> nodesBefore) { checkArgument(rootWriters.keySet().containsAll(Collections2.transform(nodesBefore.keySet(), ID_TO_CLASS)), - "Unable to handle all changes. Missing dedicated writers for: %s", - Sets.difference(nodesBefore.keySet(), rootWriters.keySet())); + "Unable to handle all changes. Missing dedicated writers for: %s", + Sets.difference(nodesBefore.keySet(), rootWriters.keySet())); } - private static final class RevertImpl implements Revert { + private static final class ReverterImpl implements Reverter { private final WriterRegistry delegatingWriterRegistry; private final List> processedNodes; private final Map, DataObject> nodesBefore; private final Map, DataObject> nodesAfter; private final WriteContext ctx; - public RevertImpl(final WriterRegistry delegatingWriterRegistry, - final List> processedNodes, - final Map, DataObject> nodesBefore, - final Map, DataObject> nodesAfter, final WriteContext ctx) { + ReverterImpl(final WriterRegistry delegatingWriterRegistry, + final List> processedNodes, + final Map, DataObject> nodesBefore, + final Map, DataObject> nodesAfter, final WriteContext ctx) { this.delegatingWriterRegistry = delegatingWriterRegistry; this.processedNodes = processedNodes; this.nodesBefore = nodesBefore; @@ -151,12 +152,11 @@ public final class DelegatingWriterRegistry implements WriterRegistry { } @Override - public void revert() throws VppException { - - final ListIterator> iterator = processedNodes.listIterator(processedNodes.size()); + public void revert() throws RevertFailedException { + final LinkedList> notReverted = new LinkedList<>(processedNodes); - while (iterator.hasPrevious()) { - final InstanceIdentifier node = iterator.previous(); + while (notReverted.size() > 0) { + final InstanceIdentifier node = notReverted.peekLast(); LOG.debug("ChangesProcessor.revertChanges() processing node={}", node); final DataObject dataBefore = nodesBefore.get(node); @@ -165,9 +165,11 @@ public final class DelegatingWriterRegistry implements WriterRegistry { // revert a change by invoking writer with reordered arguments try { delegatingWriterRegistry.update(node, dataAfter, dataBefore, ctx); - } catch (RuntimeException e) { - throw new RuntimeException(); + notReverted.removeLast(); // change was successfully reverted + } catch (Exception e) { + throw new RevertFailedException(notReverted, e); } + } } } 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