*
* @author Robert Varga
*/
+// FIXME: sealed when we have JDK17+
abstract class AbstractProxyTransaction implements Identifiable<TransactionIdentifier> {
/**
* Marker object used instead of read-type of requests, which are satisfied only once. This has a lower footprint
--- /dev/null
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.databroker.actors.dds;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.tree.api.CursorAwareDataTreeModification;
+import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModificationCursor;
+import org.opendaylight.yangtools.yang.data.tree.api.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.spi.AbstractEffectiveModelContextProvider;
+
+/**
+ * A {@link CursorAwareDataTreeModification} which does not really do anything and throws an
+ * {@link FailedDataTreeModificationException} for most of its operations. Used in case we when
+ * {@link DataTreeSnapshot#newModification()} fails, see {@link LocalReadWriteProxyTransaction} for details. Surrounding
+ * code should guard against invocation of most of these methods.
+ */
+final class FailedDataTreeModification extends AbstractEffectiveModelContextProvider
+ implements CursorAwareDataTreeModification {
+ private final @NonNull Exception cause;
+
+ FailedDataTreeModification(final EffectiveModelContext context, final Exception cause) {
+ super(context);
+ this.cause = requireNonNull(cause);
+ }
+
+ @NonNull Exception cause() {
+ return cause;
+ }
+
+ @Override
+ public void delete(final YangInstanceIdentifier path) {
+ throw ex();
+ }
+
+ @Override
+ public void merge(final YangInstanceIdentifier path, final NormalizedNode data) {
+ throw ex();
+ }
+
+ @Override
+ public void write(final YangInstanceIdentifier path, final NormalizedNode data) {
+ throw ex();
+ }
+
+ @Override
+ public void ready() {
+ // No-op
+ }
+
+ @Override
+ public void applyToCursor(final DataTreeModificationCursor cursor) {
+ throw ex();
+ }
+
+ @Override
+ public Optional<NormalizedNode> readNode(final YangInstanceIdentifier path) {
+ throw ex();
+ }
+
+ @Override
+ public CursorAwareDataTreeModification newModification() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Optional<? extends DataTreeModificationCursor> openCursor(final YangInstanceIdentifier path) {
+ throw ex();
+ }
+
+ private @NonNull FailedDataTreeModificationException ex() {
+ return new FailedDataTreeModificationException(cause);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2022 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.cluster.databroker.actors.dds;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A box {@link RuntimeException} thrown by {@link FailedDataTreeModification} from its user-facing methods.
+ */
+final class FailedDataTreeModificationException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ FailedDataTreeModificationException(final Exception cause) {
+ super(null, requireNonNull(cause), false, false);
+ }
+}
import org.opendaylight.controller.cluster.access.commands.TransactionPurgeRequest;
import org.opendaylight.controller.cluster.access.commands.TransactionRequest;
import org.opendaylight.controller.cluster.access.concepts.Response;
+import org.opendaylight.controller.cluster.access.concepts.RuntimeRequestException;
import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
import org.opendaylight.controller.cluster.datastore.util.AbstractDataTreeModificationCursor;
+import org.opendaylight.mdsal.common.api.ReadFailedException;
import org.opendaylight.yangtools.util.concurrent.FluentFutures;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
*
* @author Robert Varga
*/
+// FIXME: sealed when we have JDK17+
abstract class LocalProxyTransaction extends AbstractProxyTransaction {
private static final Logger LOG = LoggerFactory.getLogger(LocalProxyTransaction.class);
@Override
FluentFuture<Boolean> doExists(final YangInstanceIdentifier path) {
- return FluentFutures.immediateBooleanFluentFuture(readOnlyView().readNode(path).isPresent());
+ final boolean result;
+ try {
+ result = readOnlyView().readNode(path).isPresent();
+ } catch (FailedDataTreeModificationException e) {
+ return FluentFutures.immediateFailedFluentFuture(ReadFailedException.MAPPER.apply(e));
+ }
+ return FluentFutures.immediateBooleanFluentFuture(result);
}
@Override
FluentFuture<Optional<NormalizedNode>> doRead(final YangInstanceIdentifier path) {
- return FluentFutures.immediateFluentFuture(readOnlyView().readNode(path));
+ final Optional<NormalizedNode> result;
+ try {
+ result = readOnlyView().readNode(path);
+ } catch (FailedDataTreeModificationException e) {
+ return FluentFutures.immediateFailedFluentFuture(ReadFailedException.MAPPER.apply(e));
+ }
+ return FluentFutures.immediateFluentFuture(result);
}
@Override
@NonNull Response<?, ?> handleExistsRequest(final @NonNull DataTreeSnapshot snapshot,
final @NonNull ExistsTransactionRequest request) {
- return new ExistsTransactionSuccess(request.getTarget(), request.getSequence(),
- snapshot.readNode(request.getPath()).isPresent());
+ try {
+ return new ExistsTransactionSuccess(request.getTarget(), request.getSequence(),
+ snapshot.readNode(request.getPath()).isPresent());
+ } catch (FailedDataTreeModificationException e) {
+ return request.toRequestFailure(new RuntimeRequestException("Failed to access data",
+ ReadFailedException.MAPPER.apply(e)));
+ }
}
@NonNull Response<?, ?> handleReadRequest(final @NonNull DataTreeSnapshot snapshot,
final @NonNull ReadTransactionRequest request) {
- return new ReadTransactionSuccess(request.getTarget(), request.getSequence(),
- snapshot.readNode(request.getPath()));
+ try {
+ return new ReadTransactionSuccess(request.getTarget(), request.getSequence(),
+ snapshot.readNode(request.getPath()));
+ } catch (FailedDataTreeModificationException e) {
+ return request.toRequestFailure(new RuntimeRequestException("Failed to access data",
+ ReadFailedException.MAPPER.apply(e)));
+ }
}
private boolean handleReadRequest(final TransactionRequest<?> request, final Consumer<Response<?, ?>> callback) {
*/
private Exception recordedFailure;
+ @SuppressWarnings("checkstyle:IllegalCatch")
LocalReadWriteProxyTransaction(final ProxyHistory parent, final TransactionIdentifier identifier,
final DataTreeSnapshot snapshot) {
super(parent, identifier, false);
- modification = (CursorAwareDataTreeModification) snapshot.newModification();
+
+ if (snapshot instanceof FailedDataTreeModification) {
+ final var failed = (FailedDataTreeModification) snapshot;
+ recordedFailure = failed.cause();
+ modification = failed;
+ } else {
+ CursorAwareDataTreeModification mod;
+ try {
+ mod = (CursorAwareDataTreeModification) snapshot.newModification();
+ } catch (Exception e) {
+ LOG.debug("Failed to instantiate modification for {}", identifier, e);
+ recordedFailure = e;
+ mod = new FailedDataTreeModification(snapshot.getEffectiveModelContext(), e);
+ }
+ modification = mod;
+ }
}
LocalReadWriteProxyTransaction(final ProxyHistory parent, final TransactionIdentifier identifier) {
// Rebase old modification on new data tree.
final CursorAwareDataTreeModification mod = getModification();
- try (DataTreeModificationCursor cursor = mod.openCursor()) {
- request.getModification().applyToCursor(cursor);
+ if (!(mod instanceof FailedDataTreeModification)) {
+ request.getDelayedFailure().ifPresentOrElse(failure -> {
+ if (recordedFailure == null) {
+ recordedFailure = failure;
+ } else {
+ recordedFailure.addSuppressed(failure);
+ }
+ }, () -> {
+ try (DataTreeModificationCursor cursor = mod.openCursor()) {
+ request.getModification().applyToCursor(cursor);
+ }
+ });
}
if (markSealed()) {