From 8793bf7f2e02e6cd926d58c93c8100df30f57ed9 Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Wed, 10 Dec 2014 16:02:27 +0100 Subject: [PATCH] BUG-2560 Canonical write to remote netconf devices Current sequence for writing to remote netconf devices: Writable running only: - lock running or fail - edit running (unlock on fail) - unlock running Candidate only: - lock candidate -- if lock fails, try to discard changes --- if discard fails, fail --- if discard succeeds, lock candidate ---- if lock fails, fail - edit candidate (discard-changes + unlock on fail) - commit - unlock candidate Both writable running and candidate: - lock running or fail - SAME AS FOR CANDIDATE ONLY - unlock running Change-Id: Ia4e0d43f3131c4072e8505ba72ef09ff441c1fac Signed-off-by: Maros Marsalek --- .../listener/NetconfSessionCapabilities.java | 6 + .../netconf/sal/NetconfDeviceDataBroker.java | 35 +- .../netconf/sal/tx/AbstractWriteTx.java | 146 +++++++ .../sal/tx/NetconfDeviceWriteOnlyTx.java | 360 ------------------ ...fDeviceReadOnlyTx.java => ReadOnlyTx.java} | 63 +-- ...eviceReadWriteTx.java => ReadWriteTx.java} | 10 +- .../sal/tx/WriteCandidateRunningTx.java | 72 ++++ .../netconf/sal/tx/WriteCandidateTx.java | 204 ++++++++++ .../netconf/sal/tx/WriteRunningTx.java | 146 +++++++ .../connect/netconf/util/NetconfBaseOps.java | 285 ++++++++++++++ .../util/NetconfMessageTransformUtil.java | 129 +++++-- .../util/NetconfRpcFutureCallback.java | 50 +++ .../sal/tx/NetconfDeviceWriteOnlyTxTest.java | 51 ++- .../handler/ssh/client/AsyncSshHandler.java | 6 +- 14 files changed, 1119 insertions(+), 444 deletions(-) create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java delete mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java rename opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/{NetconfDeviceReadOnlyTx.java => ReadOnlyTx.java} (73%) rename opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/{NetconfDeviceReadWriteTx.java => ReadWriteTx.java} (90%) create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java create mode 100644 opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java index 09e178f5ce..d5b3778b4f 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/listener/NetconfSessionCapabilities.java @@ -88,6 +88,8 @@ public final class NetconfSessionCapabilities { .add("moduleBasedCapabilities", moduleBasedCaps) .add("rollback", isRollbackSupported()) .add("monitoring", isMonitoringSupported()) + .add("candidate", isCandidateSupported()) + .add("writableRunning", isRunningWritable()) .toString(); } @@ -99,6 +101,10 @@ public final class NetconfSessionCapabilities { return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_CANDIDATE_URI.toString()); } + public boolean isRunningWritable() { + return containsNonModuleCapability(NetconfMessageTransformUtil.NETCONF_RUNNING_WRITABLE_URI.toString()); + } + public boolean isMonitoringSupported() { return containsModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING) || containsNonModuleCapability(NetconfMessageTransformUtil.IETF_NETCONF_MONITORING.getNamespace().toString()); diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java index f3a9acd630..aa22e877a4 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/NetconfDeviceDataBroker.java @@ -18,9 +18,12 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction; import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain; import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; -import org.opendaylight.controller.sal.connect.netconf.sal.tx.NetconfDeviceReadOnlyTx; -import org.opendaylight.controller.sal.connect.netconf.sal.tx.NetconfDeviceReadWriteTx; -import org.opendaylight.controller.sal.connect.netconf.sal.tx.NetconfDeviceWriteOnlyTx; +import org.opendaylight.controller.sal.connect.netconf.sal.tx.ReadOnlyTx; +import org.opendaylight.controller.sal.connect.netconf.sal.tx.ReadWriteTx; +import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteCandidateTx; +import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteCandidateRunningTx; +import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteRunningTx; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.yangtools.concepts.ListenerRegistration; @@ -29,40 +32,48 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContext; final class NetconfDeviceDataBroker implements DOMDataBroker { private final RemoteDeviceId id; - private final RpcImplementation rpc; + private final NetconfBaseOps netconfOps; private final NetconfSessionCapabilities netconfSessionPreferences; private final DataNormalizer normalizer; - public NetconfDeviceDataBroker(final RemoteDeviceId id, final RpcImplementation rpc, final SchemaContext schemaContext, NetconfSessionCapabilities netconfSessionPreferences) { + public NetconfDeviceDataBroker(final RemoteDeviceId id, final RpcImplementation rpc, final SchemaContext schemaContext, final NetconfSessionCapabilities netconfSessionPreferences) { this.id = id; - this.rpc = rpc; + this.netconfOps = new NetconfBaseOps(rpc); this.netconfSessionPreferences = netconfSessionPreferences; normalizer = new DataNormalizer(schemaContext); } @Override public DOMDataReadOnlyTransaction newReadOnlyTransaction() { - return new NetconfDeviceReadOnlyTx(rpc, normalizer, id); + return new ReadOnlyTx(netconfOps, normalizer, id); } @Override public DOMDataReadWriteTransaction newReadWriteTransaction() { - return new NetconfDeviceReadWriteTx(newReadOnlyTransaction(), newWriteOnlyTransaction()); + return new ReadWriteTx(newReadOnlyTransaction(), newWriteOnlyTransaction()); } @Override public DOMDataWriteTransaction newWriteOnlyTransaction() { - return new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, netconfSessionPreferences.isCandidateSupported(), netconfSessionPreferences.isRollbackSupported()); + if(netconfSessionPreferences.isCandidateSupported()) { + if(netconfSessionPreferences.isRunningWritable()) { + return new WriteCandidateRunningTx(id, netconfOps, normalizer, netconfSessionPreferences); + } else { + return new WriteCandidateTx(id, netconfOps, normalizer, netconfSessionPreferences); + } + } else { + return new WriteRunningTx(id, netconfOps, normalizer, netconfSessionPreferences); + } } @Override public ListenerRegistration registerDataChangeListener(final LogicalDatastoreType store, final YangInstanceIdentifier path, final DOMDataChangeListener listener, final DataChangeScope triggeringScope) { - throw new UnsupportedOperationException("Data change listeners not supported for netconf mount point"); + throw new UnsupportedOperationException(id + ": Data change listeners not supported for netconf mount point"); } @Override public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) { - // TODO implement - throw new UnsupportedOperationException("Transaction chains not supported for netconf mount point"); + throw new UnsupportedOperationException(id + ": Transaction chains not supported for netconf mount point"); } + } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java new file mode 100644 index 0000000000..165d9c452d --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/AbstractWriteTx.java @@ -0,0 +1,146 @@ +package org.opendaylight.controller.sal.connect.netconf.sal.tx; + +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.createEditConfigStructure; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.ModifyAction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +public abstract class AbstractWriteTx implements DOMDataWriteTransaction { + protected final RemoteDeviceId id; + protected final NetconfBaseOps netOps; + protected final DataNormalizer normalizer; + protected final NetconfSessionCapabilities netconfSessionPreferences; + // Allow commit to be called only once + protected boolean finished = false; + + public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + this.netOps = netOps; + this.id = id; + this.normalizer = normalizer; + this.netconfSessionPreferences = netconfSessionPreferences; + init(); + } + + protected void checkNotFinished() { + Preconditions.checkState(!isFinished(), "%s: Transaction %s already finished", id, getIdentifier()); + } + + protected boolean isFinished() { + return finished; + } + + protected void invokeBlocking(final String msg, final Function>> op) throws NetconfDocumentedException { + try { + final RpcResult compositeNodeRpcResult = op.apply(netOps).get(1L, TimeUnit.MINUTES); + if(compositeNodeRpcResult.isSuccessful() == false) { + throw new NetconfDocumentedException(id + ": " + msg + " failed: " + compositeNodeRpcResult.getErrors(), NetconfDocumentedException.ErrorType.application, + NetconfDocumentedException.ErrorTag.operation_failed, NetconfDocumentedException.ErrorSeverity.warning); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (final ExecutionException | TimeoutException e) { + throw new NetconfDocumentedException(id + ": " + msg + " failed: " + e.getMessage(), e, NetconfDocumentedException.ErrorType.application, + NetconfDocumentedException.ErrorTag.operation_failed, NetconfDocumentedException.ErrorSeverity.warning); + } + } + + @Override + public synchronized boolean cancel() { + if(isFinished()) { + return false; + } + + finished = true; + cleanup(); + return true; + } + + protected abstract void init(); + + protected abstract void cleanup(); + + @Override + public Object getIdentifier() { + return this; + } + + @Override + public synchronized void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { + checkEditable(store); + + try { + final YangInstanceIdentifier legacyPath = ReadOnlyTx.toLegacyPath(normalizer, path, id); + final CompositeNode legacyData = normalizer.toLegacy(path, data); + editConfig( + createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE)); + } catch (final NetconfDocumentedException e) { + handleEditException(path, data, e, "putting"); + } + } + + protected abstract void handleEditException(YangInstanceIdentifier path, NormalizedNode data, NetconfDocumentedException e, String editType); + protected abstract void handleDeleteException(YangInstanceIdentifier path, NetconfDocumentedException e); + + @Override + public synchronized void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { + checkEditable(store); + + try { + final YangInstanceIdentifier legacyPath = ReadOnlyTx.toLegacyPath(normalizer, path, id); + final CompositeNode legacyData = normalizer.toLegacy(path, data); + editConfig( + createEditConfigStructure(legacyPath, Optional.absent(), Optional.fromNullable(legacyData)), Optional.absent()); + } catch (final NetconfDocumentedException e) { + handleEditException(path, data, e, "merge"); + } + } + + @Override + public synchronized void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) { + checkEditable(store); + + try { + editConfig(createEditConfigStructure( + ReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE), + Optional.absent()), Optional.of(ModifyAction.NONE)); + } catch (final NetconfDocumentedException e) { + handleDeleteException(path, e); + } + } + + @Override + public final ListenableFuture> commit() { + checkNotFinished(); + finished = true; + + return performCommit(); + } + + protected abstract ListenableFuture> performCommit(); + + private void checkEditable(final LogicalDatastoreType store) { + checkNotFinished(); + Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can edit only configuration data, not %s", store); + } + + protected abstract void editConfig(CompositeNode editStructure, Optional defaultOperation) throws NetconfDocumentedException; +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java deleted file mode 100644 index 4b53dd7c44..0000000000 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTx.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.connect.netconf.sal.tx; - -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION; -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.CheckedFuture; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; -import org.opendaylight.controller.md.sal.common.api.TransactionStatus; -import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; -import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; -import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; -import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction; -import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; -import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; -import org.opendaylight.controller.sal.core.api.RpcImplementation; -import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.RpcError; -import org.opendaylight.yangtools.yang.common.RpcResult; -import org.opendaylight.yangtools.yang.common.RpcResultBuilder; -import org.opendaylight.yangtools.yang.data.api.CompositeNode; -import org.opendaylight.yangtools.yang.data.api.ModifyAction; -import org.opendaylight.yangtools.yang.data.api.Node; -import org.opendaylight.yangtools.yang.data.api.SimpleNode; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; -import org.opendaylight.yangtools.yang.data.impl.NodeFactory; -import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction, FutureCallback> { - - private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceWriteOnlyTx.class); - - private final RemoteDeviceId id; - private final RpcImplementation rpc; - private final DataNormalizer normalizer; - - private final boolean rollbackSupported; - private final boolean candidateSupported; - private final CompositeNode targetNode; - - // Allow commit to be called only once - private final AtomicBoolean finished = new AtomicBoolean(false); - - public NetconfDeviceWriteOnlyTx(final RemoteDeviceId id, final RpcImplementation rpc, final DataNormalizer normalizer, final boolean candidateSupported, final boolean rollbackOnErrorSupported) { - this.id = id; - this.rpc = rpc; - this.normalizer = normalizer; - - this.candidateSupported = candidateSupported; - this.targetNode = getTargetNode(this.candidateSupported); - this.rollbackSupported = rollbackOnErrorSupported; - } - - @Override - public boolean cancel() { - if(isFinished()) { - return false; - } - - return discardChanges(); - } - - private boolean isFinished() { - return finished.get(); - } - - private boolean discardChanges() { - finished.set(true); - - if(candidateSupported) { - sendDiscardChanges(); - } - return true; - } - - // TODO should the edit operations be blocking ? - // TODO should the discard-changes operations be blocking ? - - @Override - public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - checkNotFinished(); - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store); - - try { - final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id); - final CompositeNode legacyData = normalizer.toLegacy(path, data); - sendEditRpc( - createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE)); - } catch (final ExecutionException e) { - LOG.warn("{}: Error putting data to {}, data: {}, discarding changes", id, path, data, e); - discardChanges(); - throw new RuntimeException(id + ": Error while replacing " + path, e); - } - } - - private void checkNotFinished() { - Preconditions.checkState(isFinished() == false, "%s: Transaction %s already finished", id, getIdentifier()); - } - - @Override - public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) { - checkNotFinished(); - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store); - - try { - final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id); - final CompositeNode legacyData = normalizer.toLegacy(path, data); - sendEditRpc( - createEditConfigStructure(legacyPath, Optional. absent(), Optional.fromNullable(legacyData)), Optional. absent()); - } catch (final ExecutionException e) { - LOG.warn("{}: Error merging data to {}, data: {}, discarding changes", id, path, data, e); - discardChanges(); - throw new RuntimeException(id + ": Error while merging " + path, e); - } - } - - @Override - public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) { - checkNotFinished(); - Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store); - - try { - sendEditRpc( - createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE), Optional.absent()), Optional.of(ModifyAction.NONE)); - } catch (final ExecutionException e) { - LOG.warn("{}: Error deleting data {}, discarding changes", id, path, e); - discardChanges(); - throw new RuntimeException(id + ": Error while deleting " + path, e); - } - } - - @Override - public CheckedFuture submit() { - final ListenableFuture commmitFutureAsVoid = Futures.transform(commit(), new Function, Void>() { - @Override - public Void apply(final RpcResult input) { - return null; - } - }); - - return Futures.makeChecked(commmitFutureAsVoid, new Function() { - @Override - public TransactionCommitFailedException apply(final Exception input) { - return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input); - } - }); - } - - @Override - public ListenableFuture> commit() { - checkNotFinished(); - finished.set(true); - - if(candidateSupported == false) { - return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build()); - } - - final ListenableFuture> rpcResult = rpc.invokeRpc( - NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT); - - final ListenableFuture> transformed = Futures.transform(rpcResult, - new Function, RpcResult>() { - @Override - public RpcResult apply(final RpcResult input) { - if (input.isSuccessful()) { - return RpcResultBuilder.success(TransactionStatus.COMMITED).build(); - } else { - final RpcResultBuilder failed = RpcResultBuilder.failed(); - for (final RpcError rpcError : input.getErrors()) { - failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(), - rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause()); - } - return failed.build(); - } - } - }); - - Futures.addCallback(transformed, this); - return transformed; - } - - @Override - public void onSuccess(final RpcResult result) { - LOG.debug("{}: Write successful, transaction: {}", id, getIdentifier()); - } - - @Override - public void onFailure(final Throwable t) { - LOG.warn("{}: Write failed, transaction {}, discarding changes", id, getIdentifier(), t); - discardChanges(); - } - - private void sendEditRpc(final CompositeNode editStructure, final Optional defaultOperation) throws ExecutionException { - final CompositeNode editConfigRequest = createEditConfigRequest(editStructure, defaultOperation); - final RpcResult rpcResult; - try { - rpcResult = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, editConfigRequest).get(); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException(id + ": Interrupted while waiting for response", e); - } - - // Check result - if(rpcResult.isSuccessful() == false) { - throw new ExecutionException( - String.format("%s: Pre-commit rpc failed, request: %s, errors: %s", id, editConfigRequest, rpcResult.getErrors()), null); - } - } - - private void sendDiscardChanges() { - final ListenableFuture> discardFuture = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT); - Futures.addCallback(discardFuture, new FutureCallback>() { - @Override - public void onSuccess(final RpcResult result) { - LOG.debug("{}: Discarding transaction: {}", id, getIdentifier()); - } - - @Override - public void onFailure(final Throwable t) { - LOG.error("{}: Discarding changes failed, transaction: {}. Device configuration might be corrupted", id, getIdentifier(), t); - throw new RuntimeException(id + ": Discarding changes failed, transaction " + getIdentifier(), t); - } - }); - } - - private CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional operation, - final Optional lastChildOverride) { - Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath); - - // Create deepest edit element with expected edit operation - CompositeNode previous = getDeepestEditElement(dataPath.getLastPathArgument(), operation, lastChildOverride); - - Iterator it = dataPath.getReversePathArguments().iterator(); - // Remove already processed deepest child - it.next(); - - // Create edit structure in reversed order - while (it.hasNext()) { - final YangInstanceIdentifier.PathArgument arg = it.next(); - final CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); - builder.setQName(arg.getNodeType()); - - addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder); - - builder.add(previous); - previous = builder.toInstance(); - } - return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.>of(previous)); - } - - private void addPredicatesToCompositeNodeBuilder(final Map predicates, final CompositeNodeBuilder builder) { - for (final Map.Entry entry : predicates.entrySet()) { - builder.addLeaf(entry.getKey(), entry.getValue()); - } - } - - private Map getPredicates(final YangInstanceIdentifier.PathArgument arg) { - Map predicates = Collections.emptyMap(); - if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) { - predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues(); - } - return predicates; - } - - private CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional operation, final Optional lastChildOverride) { - final CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); - builder.setQName(arg.getNodeType()); - - final Map predicates = getPredicates(arg); - addPredicatesToCompositeNodeBuilder(predicates, builder); - - if (operation.isPresent()) { - builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get())); - } - if (lastChildOverride.isPresent()) { - final List> children = lastChildOverride.get().getValue(); - for(final Node child : children) { - if(!predicates.containsKey(child.getKey())) { - builder.add(child); - } - } - } - - return builder.toInstance(); - } - - private CompositeNode createEditConfigRequest(final CompositeNode editStructure, final Optional defaultOperation) { - final CompositeNodeBuilder ret = ImmutableCompositeNode.builder(); - - // Target - final Node targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.>of(targetNode)); - ret.add(targetWrapperNode); - - // Default operation - if(defaultOperation.isPresent()) { - final SimpleNode defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, modifyOperationToXmlString(defaultOperation.get())); - ret.add(defOp); - } - - // Error option - if(rollbackSupported) { - ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION); - } - - ret.setQName(NETCONF_EDIT_CONFIG_QNAME); - // Edit content - ret.add(editStructure); - return ret.toInstance(); - } - - private String modifyOperationToXmlString(final ModifyAction operation) { - return operation.name().toLowerCase(); - } - - public CompositeNode getTargetNode(final boolean candidateSupported) { - if(candidateSupported) { - return ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.>of()); - } else { - return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.>of()); - } - } - - @Override - public Object getIdentifier() { - return this; - } -} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java similarity index 73% rename from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java rename to opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java index 6c46bed762..a3186f8e69 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadOnlyTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadOnlyTx.java @@ -7,20 +7,24 @@ */ package org.opendaylight.controller.sal.connect.netconf.sal.tx; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_QNAME; + import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.ExecutionException; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; -import org.opendaylight.controller.sal.core.api.RpcImplementation; import org.opendaylight.yangtools.util.concurrent.MappingCheckedFuture; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.data.api.CompositeNode; @@ -29,35 +33,44 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.ExecutionException; - -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.CONFIG_SOURCE_RUNNING; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DATA_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME; -import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure; - -public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction { +public final class ReadOnlyTx implements DOMDataReadOnlyTransaction { - private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceReadOnlyTx.class); + private static final Logger LOG = LoggerFactory.getLogger(ReadOnlyTx.class); - private final RpcImplementation rpc; + private final NetconfBaseOps netconfOps; private final DataNormalizer normalizer; private final RemoteDeviceId id; + private final FutureCallback> loggingCallback; - public NetconfDeviceReadOnlyTx(final RpcImplementation rpc, final DataNormalizer normalizer, final RemoteDeviceId id) { - this.rpc = rpc; + public ReadOnlyTx(final NetconfBaseOps netconfOps, final DataNormalizer normalizer, final RemoteDeviceId id) { + this.netconfOps = netconfOps; this.normalizer = normalizer; this.id = id; + // Simple logging callback to log result of read operation + loggingCallback = new FutureCallback>() { + @Override + public void onSuccess(final RpcResult result) { + if(result.isSuccessful()) { + LOG.trace("{}: Reading data successful", id); + } else { + LOG.warn("{}: Reading data unsuccessful: {}", id, result.getErrors()); + } + + } + + @Override + public void onFailure(final Throwable t) { + LOG.warn("{}: Reading data failed", id, t); + } + }; } private CheckedFuture>, ReadFailedException> readConfigurationData( final YangInstanceIdentifier path) { - final ListenableFuture> future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, - NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, CONFIG_SOURCE_RUNNING, toFilterStructure(path))); - - final ListenableFuture>> transformedFuture = Futures.transform(future, new Function, Optional>>() { + final ListenableFuture> configRunning = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path)); + // Find data node and normalize its content + final ListenableFuture>> transformedFuture = Futures.transform(configRunning, new Function, Optional>>() { @Override public Optional> apply(final RpcResult result) { checkReadSuccess(result, path); @@ -77,7 +90,7 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction private void checkReadSuccess(final RpcResult result, final YangInstanceIdentifier path) { try { Preconditions.checkArgument(result.isSuccessful(), "%s: Unable to read data: %s, errors: %s", id, path, result.getErrors()); - } catch (IllegalArgumentException e) { + } catch (final IllegalArgumentException e) { LOG.warn("{}: Unable to read data: {}, errors: {}", id, path, result.getErrors()); throw e; } @@ -97,9 +110,10 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction private CheckedFuture>, ReadFailedException> readOperationalData( final YangInstanceIdentifier path) { - final ListenableFuture> future = rpc.invokeRpc(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, toFilterStructure(path))); + final ListenableFuture> configCandidate = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path)); - final ListenableFuture>> transformedFuture = Futures.transform(future, new Function, Optional>>() { + // Find data node and normalize its content + final ListenableFuture>> transformedFuture = Futures.transform(configCandidate, new Function, Optional>>() { @Override public Optional> apply(final RpcResult result) { checkReadSuccess(result, path); @@ -138,10 +152,9 @@ public final class NetconfDeviceReadOnlyTx implements DOMDataReadOnlyTransaction throw new IllegalArgumentException(String.format("%s, Cannot read data %s for %s datastore, unknown datastore type", id, path, store)); } - @Override public CheckedFuture exists( - LogicalDatastoreType store, - YangInstanceIdentifier path) { - CheckedFuture>, ReadFailedException> + @Override + public CheckedFuture exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) { + final CheckedFuture>, ReadFailedException> data = read(store, path); try { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadWriteTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadWriteTx.java similarity index 90% rename from opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadWriteTx.java rename to opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadWriteTx.java index 11362a2f9b..6da1998c04 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceReadWriteTx.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/ReadWriteTx.java @@ -26,12 +26,12 @@ import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import java.util.concurrent.ExecutionException; -public class NetconfDeviceReadWriteTx implements DOMDataReadWriteTransaction { +public class ReadWriteTx implements DOMDataReadWriteTransaction { private final DOMDataReadTransaction delegateReadTx; private final DOMDataWriteTransaction delegateWriteTx; - public NetconfDeviceReadWriteTx(final DOMDataReadTransaction delegateReadTx, final DOMDataWriteTransaction delegateWriteTx) { + public ReadWriteTx(final DOMDataReadTransaction delegateReadTx, final DOMDataWriteTransaction delegateWriteTx) { this.delegateReadTx = delegateReadTx; this.delegateWriteTx = delegateWriteTx; } @@ -73,9 +73,9 @@ public class NetconfDeviceReadWriteTx implements DOMDataReadWriteTransaction { } @Override public CheckedFuture exists( - LogicalDatastoreType store, - YangInstanceIdentifier path) { - CheckedFuture>, ReadFailedException> + final LogicalDatastoreType store, + final YangInstanceIdentifier path) { + final CheckedFuture>, ReadFailedException> data = read(store, path); try { diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java new file mode 100644 index 0000000000..4a9a9398d0 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateRunningTx.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.connect.netconf.sal.tx; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.ListenableFuture; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tx implementation for netconf devices that support only candidate datastore and writable running + * The sequence goes exactly as with only candidate supported, with one addition: + *
    + *
  • Running datastore is locked as the first thing and this lock has to succeed
  • + *
+ */ +public class WriteCandidateRunningTx extends WriteCandidateTx { + + private static final Logger LOG = LoggerFactory.getLogger(WriteCandidateRunningTx.class); + + public WriteCandidateRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + super(id, netOps, normalizer, netconfSessionPreferences); + } + + @Override + protected synchronized void init() { + lockRunning(); + super.init(); + } + + @Override + protected void cleanupOnSuccess() { + super.cleanupOnSuccess(); + unlockRunning(); + } + + private void lockRunning() { + try { + invokeBlocking("Lock running", new Function>>() { + @Override + public ListenableFuture> apply(final NetconfBaseOps input) { + return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id)); + } + }); + } catch (final NetconfDocumentedException e) { + LOG.warn("{}: Failed to lock running. Failed to initialize transaction", e); + finished = true; + throw new RuntimeException(id + ": Failed to lock running. Failed to initialize transaction", e); + } + } + + /** + * This has to be non blocking since it is called from a callback on commit and its netty threadpool that is really sensitive to blocking calls + */ + private void unlockRunning() { + netOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java new file mode 100644 index 0000000000..0ea6298398 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteCandidateTx.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.connect.netconf.sal.tx; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.ModifyAction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tx implementation for netconf devices that support only candidate datastore and no writable running + * The sequence goes as: + *
    + *
  1. Lock candidate datastore on tx construction + *
      + *
    • Lock has to succeed, if it does not, an attempt to discard changes is made + *
    • Discard changes has to succeed + *
    • If discard is successful, lock is reattempted + *
    • Second lock attempt has to succeed + *
    + *
  2. Edit-config in candidate N times + *
      + *
    • If any issue occurs during edit, datastore is discarded using discard-changes rpc, unlocked and an exception is thrown async + *
    + *
  3. Commit and Unlock candidate datastore async + *
+ */ +public class WriteCandidateTx extends AbstractWriteTx { + + private static final Logger LOG = LoggerFactory.getLogger(WriteCandidateTx.class); + + private static final Function, RpcResult> RPC_RESULT_TO_TX_STATUS = new Function, RpcResult>() { + @Override + public RpcResult apply(final RpcResult input) { + if (input.isSuccessful()) { + return RpcResultBuilder.success(TransactionStatus.COMMITED).build(); + } else { + final RpcResultBuilder failed = RpcResultBuilder.failed(); + for (final RpcError rpcError : input.getErrors()) { + failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(), + rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause()); + } + return failed.build(); + } + } + }; + + public WriteCandidateTx(final RemoteDeviceId id, final NetconfBaseOps rpc, final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + super(rpc, id, normalizer, netconfSessionPreferences); + } + + @Override + protected synchronized void init() { + LOG.trace("{}: Initializing {} transaction", id, getClass().getSimpleName()); + + try { + lock(); + } catch (final NetconfDocumentedException e) { + try { + LOG.warn("{}: Failed to lock candidate, attempting discard changes", id); + discardChanges(); + LOG.warn("{}: Changes discarded successfully, attempting lock", id); + lock(); + } catch (final NetconfDocumentedException secondE) { + LOG.error("{}: Failed to prepare candidate. Failed to initialize transaction", id, secondE); + throw new RuntimeException(id + ": Failed to prepare candidate. Failed to initialize transaction", secondE); + } + } + } + + private void lock() throws NetconfDocumentedException { + try { + invokeBlocking("Lock candidate", new Function>>() { + @Override + public ListenableFuture> apply(final NetconfBaseOps input) { + return input.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id)); + } + }); + } catch (final NetconfDocumentedException e) { + LOG.warn("{}: Failed to lock candidate", id, e); + throw e; + } + } + + @Override + protected void cleanup() { + discardChanges(); + cleanupOnSuccess(); + } + + @Override + protected void handleEditException(final YangInstanceIdentifier path, final NormalizedNode data, final NetconfDocumentedException e, final String editType) { + LOG.warn("{}: Error " + editType + " data to (candidate){}, data: {}, canceling", id, path, data, e); + cancel(); + throw new RuntimeException(id + ": Error while " + editType + ": (candidate)" + path, e); + } + + @Override + protected void handleDeleteException(final YangInstanceIdentifier path, final NetconfDocumentedException e) { + LOG.warn("{}: Error deleting data (candidate){}, canceling", id, path, e); + cancel(); + throw new RuntimeException(id + ": Error while deleting (candidate)" + path, e); + } + + @Override + public synchronized CheckedFuture submit() { + final ListenableFuture commitFutureAsVoid = Futures.transform(commit(), new Function, Void>() { + @Override + public Void apply(final RpcResult input) { + return null; + } + }); + + return Futures.makeChecked(commitFutureAsVoid, new Function() { + @Override + public TransactionCommitFailedException apply(final Exception input) { + return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input); + } + }); + } + + /** + * This has to be non blocking since it is called from a callback on commit and its netty threadpool that is really sensitive to blocking calls + */ + private void discardChanges() { + netOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id)); + } + + @Override + public synchronized ListenableFuture> performCommit() { + final ListenableFuture> rpcResult = netOps.commit(new NetconfRpcFutureCallback("Commit", id) { + @Override + public void onSuccess(final RpcResult result) { + super.onSuccess(result); + LOG.debug("{}: Write successful, transaction: {}. Unlocking", id, getIdentifier()); + cleanupOnSuccess(); + } + + @Override + protected void onUnsuccess(final RpcResult result) { + LOG.error("{}: Write failed, transaction {}, discarding changes, unlocking: {}", id, getIdentifier(), result.getErrors()); + cleanup(); + } + + @Override + public void onFailure(final Throwable t) { + LOG.error("{}: Write failed, transaction {}, discarding changes, unlocking", id, getIdentifier(), t); + cleanup(); + } + }); + + return Futures.transform(rpcResult, RPC_RESULT_TO_TX_STATUS); + } + + protected void cleanupOnSuccess() { + unlock(); + } + + @Override + protected void editConfig(final CompositeNode editStructure, final Optional defaultOperation) throws NetconfDocumentedException { + invokeBlocking("Edit candidate", new Function>>() { + @Override + public ListenableFuture> apply(final NetconfBaseOps input) { + return defaultOperation.isPresent() + ? input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, defaultOperation.get(), + netconfSessionPreferences.isRollbackSupported()) + : input.editConfigCandidate(new NetconfRpcFutureCallback("Edit candidate", id), editStructure, + netconfSessionPreferences.isRollbackSupported()); + } + }); + } + + /** + * This has to be non blocking since it is called from a callback on commit and its netty threadpool that is really sensitive to blocking calls + */ + private void unlock() { + netOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id)); + } + +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java new file mode 100644 index 0000000000..28173b1da3 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/WriteRunningTx.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.connect.netconf.sal.tx; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.util.concurrent.CheckedFuture; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; +import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.netconf.api.NetconfDocumentedException; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfRpcFutureCallback; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.ModifyAction; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Tx implementation for netconf devices that support only writable-running with no candidate + * The sequence goes as: + *
    + *
  1. Lock running datastore on tx construction + *
      + *
    • Lock has to succeed, if it does not, transaction is failed + *
    + *
  2. Edit-config in running N times + *
      + *
    • If any issue occurs during edit, datastore is unlocked and an exception is thrown + *
    + *
  3. Unlock running datastore on tx commit + *
+ */ +public class WriteRunningTx extends AbstractWriteTx { + + private static final Logger LOG = LoggerFactory.getLogger(WriteRunningTx.class); + + public WriteRunningTx(final RemoteDeviceId id, final NetconfBaseOps netOps, + final DataNormalizer normalizer, final NetconfSessionCapabilities netconfSessionPreferences) { + super(netOps, id, normalizer, netconfSessionPreferences); + } + + @Override + protected synchronized void init() { + lock(); + } + + private void lock() { + try { + invokeBlocking("Lock running", new Function>>() { + @Override + public ListenableFuture> apply(final NetconfBaseOps input) { + return input.lockRunning(new NetconfRpcFutureCallback("Lock running", id)); + } + }); + } catch (final NetconfDocumentedException e) { + LOG.warn("{}: Failed to initialize netconf transaction (lock running)", e); + finished = true; + throw new RuntimeException(id + ": Failed to initialize netconf transaction (lock running)", e); + } + } + + @Override + protected void cleanup() { + unlock(); + } + + @Override + protected void handleEditException(final YangInstanceIdentifier path, final NormalizedNode data, final NetconfDocumentedException e, final String editType) { + LOG.warn("{}: Error " + editType + " data to (running){}, data: {}, canceling", id, path, data, e); + cancel(); + throw new RuntimeException(id + ": Error while " + editType + ": (running)" + path, e); + } + + @Override + protected void handleDeleteException(final YangInstanceIdentifier path, final NetconfDocumentedException e) { + LOG.warn("{}: Error deleting data (running){}, canceling", id, path, e); + cancel(); + throw new RuntimeException(id + ": Error while deleting (running)" + path, e); + } + + @Override + public synchronized CheckedFuture submit() { + final ListenableFuture commmitFutureAsVoid = Futures.transform(commit(), new Function, Void>() { + @Override + public Void apply(final RpcResult input) { + return null; + } + }); + + return Futures.makeChecked(commmitFutureAsVoid, new Function() { + @Override + public TransactionCommitFailedException apply(final Exception input) { + return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input); + } + }); + } + + @Override + public synchronized ListenableFuture> performCommit() { + unlock(); + return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build()); + } + + @Override + protected void editConfig(final CompositeNode editStructure, final Optional defaultOperation) throws NetconfDocumentedException { + invokeBlocking("Edit running", new Function>>() { + @Override + public ListenableFuture> apply(final NetconfBaseOps input) { + return defaultOperation.isPresent() + ? input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, defaultOperation.get(), + netconfSessionPreferences.isRollbackSupported()) + : input.editConfigRunning(new NetconfRpcFutureCallback("Edit running", id), editStructure, + netconfSessionPreferences.isRollbackSupported()); + } + }); + } + + private void unlock() { + try { + invokeBlocking("Unlocking running", new Function>>() { + @Override + public ListenableFuture> apply(final NetconfBaseOps input) { + return input.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)); + } + }); + } catch (final NetconfDocumentedException e) { + LOG.warn("{}: Failed to unlock running datastore", e); + throw new RuntimeException(id + ": Failed to unlock running datastore", e); + } + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java new file mode 100644 index 0000000000..8ac8a48b45 --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfBaseOps.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.connect.netconf.util; + +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_CONFIG_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_GET_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_LOCK_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_SOURCE_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.toFilterStructure; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Collections; +import org.opendaylight.controller.sal.core.api.RpcImplementation; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.ModifyAction; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.SimpleNode; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl; +import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder; + +/** + * Provides base operations for netconf e.g. get, get-config, edit-config, (un)lock, commit etc. + * According to RFC-6241 + */ +public final class NetconfBaseOps { + + private final RpcImplementation rpc; + + public NetconfBaseOps(final RpcImplementation rpc) { + this.rpc = rpc; + } + + public ListenableFuture> lock(final FutureCallback> callback, final QName datastore) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(datastore); + + final ListenableFuture> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(datastore)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> lockCandidate(final FutureCallback> callback) { + final ListenableFuture> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(NETCONF_CANDIDATE_QNAME)); + Futures.addCallback(future, callback); + return future; + } + + + public ListenableFuture> lockRunning(final FutureCallback> callback) { + final ListenableFuture> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(NETCONF_RUNNING_QNAME)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> unlock(final FutureCallback> callback, final QName datastore) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(datastore); + + final ListenableFuture> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(datastore)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> unlockRunning(final FutureCallback> callback) { + final ListenableFuture> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(NETCONF_RUNNING_QNAME)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> unlockCandidate(final FutureCallback> callback) { + final ListenableFuture> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(NETCONF_CANDIDATE_QNAME)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> discardChanges(final FutureCallback> callback) { + Preconditions.checkNotNull(callback); + + final ListenableFuture> future = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> commit(final FutureCallback> callback) { + Preconditions.checkNotNull(callback); + + final ListenableFuture> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> validate(final FutureCallback> callback, final QName datastore) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(datastore); + + final ListenableFuture> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME, getValidateContent(datastore)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> validateCandidate(final FutureCallback> callback) { + return validate(callback, NETCONF_CANDIDATE_QNAME); + } + + + public ListenableFuture> validateRunning(final FutureCallback> callback) { + return validate(callback, NETCONF_RUNNING_QNAME); + } + + public ListenableFuture> copyConfig(final FutureCallback> callback, final QName source, final QName target) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(source); + Preconditions.checkNotNull(target); + + final ListenableFuture> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME, getCopyConfigContent(source, target)); + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> copyRunningToCandidate(final FutureCallback> callback) { + return copyConfig(callback, NETCONF_RUNNING_QNAME, NETCONF_CANDIDATE_QNAME); + } + + public ListenableFuture> getConfig(final FutureCallback> callback, final QName datastore, final Optional filterPath) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(datastore); + + final ListenableFuture> future; + if (filterPath.isPresent()) { + final Node node = toFilterStructure(filterPath.get()); + future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, + NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, getSourceNode(datastore), node)); + } else { + future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME, + NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, getSourceNode(datastore))); + } + + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> getConfigRunning(final FutureCallback> callback, final Optional filterPath) { + return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath); + } + + public ListenableFuture> getConfigCandidate(final FutureCallback> callback, final Optional filterPath) { + return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath); + } + + public ListenableFuture> get(final FutureCallback> callback, final QName datastore, final Optional filterPath) { + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(datastore); + + final ListenableFuture> future; + if (filterPath.isPresent()) { + final Node node = toFilterStructure(filterPath.get()); + future = rpc.invokeRpc(NETCONF_GET_QNAME, + NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, getSourceNode(datastore), node)); + } else { + future = rpc.invokeRpc(NETCONF_GET_QNAME, + NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, getSourceNode(datastore))); + } + + Futures.addCallback(future, callback); + return future; + } + + public ListenableFuture> getRunning(final FutureCallback> callback, final Optional filterPath) { + return get(callback, NETCONF_RUNNING_QNAME, filterPath); + } + + public ListenableFuture> getCandidate(final FutureCallback> callback, final Optional filterPath) { + return get(callback, NETCONF_CANDIDATE_QNAME, filterPath); + } + + + public ListenableFuture> editConfigCandidate(final FutureCallback> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) { + return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.of(modifyAction), rollback); + } + + public ListenableFuture> editConfigCandidate(final FutureCallback> callback, final CompositeNode editStructure, final boolean rollback) { + return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.absent(), rollback); + } + + public ListenableFuture> editConfigRunning(final FutureCallback> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) { + return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.of(modifyAction), rollback); + } + + public ListenableFuture> editConfigRunning(final FutureCallback> callback, final CompositeNode editStructure, final boolean rollback) { + return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.absent(), rollback); + } + + public ListenableFuture> editConfig(final FutureCallback> callback, final QName datastore, final CompositeNode editStructure, final Optional modifyAction, final boolean rollback) { + Preconditions.checkNotNull(editStructure); + Preconditions.checkNotNull(callback); + Preconditions.checkNotNull(datastore); + + final ListenableFuture> future = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, getEditConfigContent(datastore, editStructure, modifyAction, rollback)); + + Futures.addCallback(future, callback); + return future; + } + + private CompositeNode getEditConfigContent(final QName datastore, final CompositeNode editStructure, final Optional defaultOperation, final boolean rollback) { + final CompositeNodeBuilder ret = ImmutableCompositeNode.builder(); + + // Target + ret.add(getTargetNode(datastore)); + + // Default operation + if(defaultOperation.isPresent()) { + final SimpleNode defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, NetconfMessageTransformUtil.modifyOperationToXmlString(defaultOperation.get())); + ret.add(defOp); + } + + // Error option + if(rollback) { + ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION); + } + + ret.setQName(NETCONF_EDIT_CONFIG_QNAME); + // Edit content + ret.add(editStructure); + return ret.toInstance(); + } + + private static CompositeNode getSourceNode(final QName datastore) { + return NodeFactory.createImmutableCompositeNode(NETCONF_SOURCE_QNAME, null, + Collections.> singletonList(new SimpleNodeTOImpl<>(datastore, null, null))); + } + + + public static CompositeNode getLockContent(final QName datastore) { + return NodeFactory.createImmutableCompositeNode(NETCONF_LOCK_QNAME, null, Collections.>singletonList( + getTargetNode(datastore))); + } + + private static CompositeNode getTargetNode(final QName datastore) { + return NodeFactory.createImmutableCompositeNode(NETCONF_TARGET_QNAME, null, Collections.>singletonList( + NodeFactory.createImmutableSimpleNode(datastore, null, null) + )); + } + + public static CompositeNode getCopyConfigContent(final QName source, final QName target) { + return NodeFactory.createImmutableCompositeNode(NETCONF_LOCK_QNAME, null, + Lists.> newArrayList(getTargetNode(target), getSourceNode(source))); + } + + public static CompositeNode getValidateContent(final QName source) { + return NodeFactory.createImmutableCompositeNode(NETCONF_VALIDATE_QNAME, null, Lists.> newArrayList(getSourceNode(source))); + } + + public static CompositeNode getUnLockContent(final QName preferedDatastore) { + return NodeFactory.createImmutableCompositeNode(NETCONF_UNLOCK_QNAME, null, Collections.>singletonList( + getTargetNode(preferedDatastore))); + } + +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java index e3a7441caf..9eba24179f 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfMessageTransformUtil.java @@ -14,6 +14,7 @@ import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.net.URI; import java.util.ArrayList; @@ -31,6 +32,7 @@ import org.opendaylight.yangtools.yang.common.RpcError; import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity; import org.opendaylight.yangtools.yang.common.RpcResultBuilder; import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.ModifyAction; import org.opendaylight.yangtools.yang.data.api.Node; import org.opendaylight.yangtools.yang.data.api.SimpleNode; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; @@ -59,36 +61,42 @@ public class NetconfMessageTransformUtil { public static final QName IETF_NETCONF_MONITORING_SCHEMA_VERSION = QName.create(IETF_NETCONF_MONITORING, "version"); public static final QName IETF_NETCONF_MONITORING_SCHEMA_NAMESPACE = QName.create(IETF_NETCONF_MONITORING, "namespace"); - public static final URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); - public static final QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf"); - public static final QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data"); - public static final QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply"); - public static final QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option"); - public static final QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running"); - static final List> RUNNING = Collections.> singletonList(new SimpleNodeTOImpl<>(NETCONF_RUNNING_QNAME, null, null)); - public static final QName NETCONF_SOURCE_QNAME = QName.create(NETCONF_QNAME, "source"); - public static final CompositeNode CONFIG_SOURCE_RUNNING = new CompositeNodeTOImpl(NETCONF_SOURCE_QNAME, null, RUNNING); - public static final QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate"); - public static final QName NETCONF_TARGET_QNAME = QName.create(NETCONF_QNAME, "target"); - public static final QName NETCONF_CONFIG_QNAME = QName.create(NETCONF_QNAME, "config"); - public static final QName NETCONF_COMMIT_QNAME = QName.create(NETCONF_QNAME, "commit"); - public static final QName NETCONF_OPERATION_QNAME = QName.create(NETCONF_QNAME, "operation"); - public static final QName NETCONF_DEFAULT_OPERATION_QNAME = QName.create(NETCONF_OPERATION_QNAME, "default-operation"); - public static final QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config"); - public static final QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config"); - public static final QName NETCONF_DISCARD_CHANGES_QNAME = QName.create(NETCONF_QNAME, "discard-changes"); - public static final QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type"); - public static final QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter"); - public static final QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get"); - public static final QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc"); - - public static final URI NETCONF_ROLLBACK_ON_ERROR_URI = URI + public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0"); + public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf"); + public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data"); + public static QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply"); + public static QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option"); + public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running"); + public static QName NETCONF_SOURCE_QNAME = QName.create(NETCONF_QNAME, "source"); + public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate"); + public static QName NETCONF_TARGET_QNAME = QName.create(NETCONF_QNAME, "target"); + public static QName NETCONF_CONFIG_QNAME = QName.create(NETCONF_QNAME, "config"); + public static QName NETCONF_COMMIT_QNAME = QName.create(NETCONF_QNAME, "commit"); + public static QName NETCONF_VALIDATE_QNAME = QName.create(NETCONF_QNAME, "validate"); + public static QName NETCONF_COPY_CONFIG_QNAME = QName.create(NETCONF_QNAME, "copy-config"); + public static QName NETCONF_OPERATION_QNAME = QName.create(NETCONF_QNAME, "operation"); + public static QName NETCONF_DEFAULT_OPERATION_QNAME = QName.create(NETCONF_OPERATION_QNAME, "default-operation"); + public static QName NETCONF_EDIT_CONFIG_QNAME = QName.create(NETCONF_QNAME, "edit-config"); + public static QName NETCONF_GET_CONFIG_QNAME = QName.create(NETCONF_QNAME, "get-config"); + public static QName NETCONF_DISCARD_CHANGES_QNAME = QName.create(NETCONF_QNAME, "discard-changes"); + public static QName NETCONF_TYPE_QNAME = QName.create(NETCONF_QNAME, "type"); + public static QName NETCONF_FILTER_QNAME = QName.create(NETCONF_QNAME, "filter"); + public static QName NETCONF_GET_QNAME = QName.create(NETCONF_QNAME, "get"); + public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc"); + + public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI .create("urn:ietf:params:netconf:capability:rollback-on-error:1.0"); - public static final String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error"; + public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error"; - public static final URI NETCONF_CANDIDATE_URI = URI + public static URI NETCONF_CANDIDATE_URI = URI .create("urn:ietf:params:netconf:capability:candidate:1.0"); + public static URI NETCONF_RUNNING_WRITABLE_URI = URI + .create("urn:ietf:params:netconf:capability:writable-running:1.0"); + + public static QName NETCONF_LOCK_QNAME = QName.create(NETCONF_QNAME, "lock"); + public static QName NETCONF_UNLOCK_QNAME = QName.create(NETCONF_QNAME, "unlock"); + // Discard changes message public static final CompositeNode DISCARD_CHANGES_RPC_CONTENT = NodeFactory.createImmutableCompositeNode(NETCONF_DISCARD_CHANGES_QNAME, null, Collections.>emptyList()); @@ -116,7 +124,7 @@ public class NetconfMessageTransformUtil { static Node toNode(final YangInstanceIdentifier.NodeIdentifierWithPredicates argument, final Node node) { final List> list = new ArrayList<>(); for (final Map.Entry arg : argument.getKeyValues().entrySet()) { - list.add(new SimpleNodeTOImpl<>(arg.getKey(), null, arg.getValue())); + list.add(new SimpleNodeTOImpl(arg.getKey(), null, arg.getValue())); } if (node != null) { list.add(node); @@ -411,4 +419,71 @@ public class NetconfMessageTransformUtil { } return current; } + + public static String modifyOperationToXmlString(final ModifyAction operation) { + return operation.name().toLowerCase(); + } + + + public static CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional operation, + final Optional lastChildOverride) { + Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath); + + List reversedPath = Lists.reverse(dataPath.getPath()); + + // Create deepest edit element with expected edit operation + CompositeNode previous = getDeepestEditElement(reversedPath.get(0), operation, lastChildOverride); + + // Remove already processed deepest child + reversedPath = Lists.newArrayList(reversedPath); + reversedPath.remove(0); + + // Create edit structure in reversed order + for (final YangInstanceIdentifier.PathArgument arg : reversedPath) { + final CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); + builder.setQName(arg.getNodeType()); + + addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder); + + builder.add(previous); + previous = builder.toInstance(); + } + return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.>of(previous)); + } + + public static void addPredicatesToCompositeNodeBuilder(final Map predicates, final CompositeNodeBuilder builder) { + for (final Map.Entry entry : predicates.entrySet()) { + builder.addLeaf(entry.getKey(), entry.getValue()); + } + } + + public static Map getPredicates(final YangInstanceIdentifier.PathArgument arg) { + Map predicates = Collections.emptyMap(); + if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) { + predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues(); + } + return predicates; + } + + public static CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional operation, final Optional lastChildOverride) { + final CompositeNodeBuilder builder = ImmutableCompositeNode.builder(); + builder.setQName(arg.getNodeType()); + + final Map predicates = getPredicates(arg); + addPredicatesToCompositeNodeBuilder(predicates, builder); + + if (operation.isPresent()) { + builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get())); + } + if (lastChildOverride.isPresent()) { + final List> children = lastChildOverride.get().getValue(); + for(final Node child : children) { + if(!predicates.containsKey(child.getKey())) { + builder.add(child); + } + } + } + + return builder.toInstance(); + } } diff --git a/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java new file mode 100644 index 0000000000..6e1e9d764f --- /dev/null +++ b/opendaylight/md-sal/sal-netconf-connector/src/main/java/org/opendaylight/controller/sal/connect/netconf/util/NetconfRpcFutureCallback.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. 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.sal.connect.netconf.util; + +import com.google.common.util.concurrent.FutureCallback; +import org.opendaylight.controller.sal.connect.netconf.sal.tx.WriteRunningTx; +import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple Netconf rpc logging callback + */ +public class NetconfRpcFutureCallback implements FutureCallback> { + private static final Logger LOG = LoggerFactory.getLogger(WriteRunningTx.class); + + private final String type; + private final RemoteDeviceId id; + + public NetconfRpcFutureCallback(final String prefix, final RemoteDeviceId id) { + this.type = prefix; + this.id = id; + } + + @Override + public void onSuccess(final RpcResult result) { + if(result.isSuccessful()) { + LOG.trace("{}: " + type + " invoked successfully", id); + } else { + onUnsuccess(result); + } + } + + protected void onUnsuccess(final RpcResult result) { + LOG.warn("{}: " + type + " invoked unsuccessfully: {}", id, result.getErrors()); + } + + @Override + public void onFailure(final Throwable t) { + LOG.warn("{}: " + type + " failed.", id, t); + } +} diff --git a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java index 0607e4b6da..ce97541fe4 100644 --- a/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java +++ b/opendaylight/md-sal/sal-netconf-connector/src/test/java/org/opendaylight/controller/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java @@ -2,21 +2,27 @@ package org.opendaylight.controller.sal.connect.netconf.sal.tx; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.inOrder; import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME; +import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.Collections; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer; +import org.opendaylight.controller.sal.connect.netconf.listener.NetconfSessionCapabilities; +import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps; import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.controller.sal.connect.util.RemoteDeviceId; import org.opendaylight.controller.sal.core.api.RpcImplementation; @@ -40,8 +46,11 @@ public class NetconfDeviceWriteOnlyTxTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - doReturn(Futures.>immediateFailedFuture(new IllegalStateException("Failed tx"))) - .doReturn(Futures.immediateFuture(RpcResultBuilder.success().build())) + ListenableFuture> successFuture = Futures.immediateFuture(RpcResultBuilder.success().build()); + + doReturn(successFuture) + .doReturn(Futures.>immediateFailedFuture(new IllegalStateException("Failed tx"))) + .doReturn(successFuture) .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class)); yangIId = YangInstanceIdentifier.builder().node(QName.create("namespace", "2012-12-12", "name")).build(); @@ -49,31 +58,45 @@ public class NetconfDeviceWriteOnlyTxTest { } @Test - public void testDiscardCahnges() { - final NetconfDeviceWriteOnlyTx tx = new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, true, true); + public void testDiscardChanges() { + final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc), normalizer, + NetconfSessionCapabilities.fromStrings(Collections.emptySet())); final CheckedFuture submitFuture = tx.submit(); try { submitFuture.checkedGet(); } catch (final TransactionCommitFailedException e) { // verify discard changes was sent - verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT); + final InOrder inOrder = inOrder(rpc); + inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_LOCK_QNAME, NetconfBaseOps.getLockContent(NETCONF_CANDIDATE_QNAME)); + inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT); + inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT); + inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME, NetconfBaseOps.getUnLockContent(NETCONF_CANDIDATE_QNAME)); return; } fail("Submit should fail"); } - @Test - public void testDiscardCahngesNotSentWithoutCandidate() { + public void testDiscardChangesNotSentWithoutCandidate() { doReturn(Futures.immediateFuture(RpcResultBuilder.success().build())) .doReturn(Futures.>immediateFailedFuture(new IllegalStateException("Failed tx"))) .when(rpc).invokeRpc(any(QName.class), any(CompositeNode.class)); - final NetconfDeviceWriteOnlyTx tx = new NetconfDeviceWriteOnlyTx(id, rpc, normalizer, false, true); - tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId); - verify(rpc).invokeRpc(eq(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), any(CompositeNode.class)); - verifyNoMoreInteractions(rpc); + final WriteRunningTx tx = new WriteRunningTx(id, new NetconfBaseOps(rpc), normalizer, + NetconfSessionCapabilities.fromStrings(Collections.emptySet())); + try { + tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId); + } catch (final Exception e) { + // verify discard changes was sent + final InOrder inOrder = inOrder(rpc); + inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_LOCK_QNAME, NetconfBaseOps.getLockContent(NETCONF_RUNNING_QNAME)); + inOrder.verify(rpc).invokeRpc(same(NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME), any(CompositeNode.class)); + inOrder.verify(rpc).invokeRpc(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME, NetconfBaseOps.getUnLockContent(NETCONF_RUNNING_QNAME)); + return; + } + + fail("Delete should fail"); } } diff --git a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java index 05cd598cdc..cda940f9b7 100644 --- a/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java +++ b/opendaylight/netconf/netconf-netty-util/src/main/java/org/opendaylight/controller/netconf/nettyutil/handler/ssh/client/AsyncSshHandler.java @@ -101,7 +101,11 @@ public class AsyncSshHandler extends ChannelOutboundHandlerAdapter { if (future.isSuccess()) { handleSshAuthenticated(session, ctx); } else { - handleSshSetupFailure(ctx, future.getException()); + // Exception does not have to be set in the future, add simple exception in such case + final Throwable exception = future.getException() == null ? + new IllegalStateException("Authentication failed") : + future.getException(); + handleSshSetupFailure(ctx, exception); } } }); -- 2.36.6