.add("moduleBasedCapabilities", moduleBasedCaps)
.add("rollback", isRollbackSupported())
.add("monitoring", isMonitoringSupported())
+ .add("candidate", isCandidateSupported())
+ .add("writableRunning", isRunningWritable())
.toString();
}
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());
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;
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<DOMDataChangeListener> 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");
}
+
}
--- /dev/null
+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<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>> op) throws NetconfDocumentedException {
+ try {
+ final RpcResult<CompositeNode> 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.<ModifyAction>absent(), Optional.fromNullable(legacyData)), Optional.<ModifyAction>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.<CompositeNode>absent()), Optional.of(ModifyAction.NONE));
+ } catch (final NetconfDocumentedException e) {
+ handleDeleteException(path, e);
+ }
+ }
+
+ @Override
+ public final ListenableFuture<RpcResult<TransactionStatus>> commit() {
+ checkNotFinished();
+ finished = true;
+
+ return performCommit();
+ }
+
+ protected abstract ListenableFuture<RpcResult<TransactionStatus>> 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<ModifyAction> defaultOperation) throws NetconfDocumentedException;
+}
+++ /dev/null
-/*
- * 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<RpcResult<TransactionStatus>> {
-
- 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.<ModifyAction> absent(), Optional.fromNullable(legacyData)), Optional.<ModifyAction> 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.<CompositeNode>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<Void, TransactionCommitFailedException> submit() {
- final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
- @Override
- public Void apply(final RpcResult<TransactionStatus> input) {
- return null;
- }
- });
-
- return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
- @Override
- public TransactionCommitFailedException apply(final Exception input) {
- return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
- }
- });
- }
-
- @Override
- public ListenableFuture<RpcResult<TransactionStatus>> commit() {
- checkNotFinished();
- finished.set(true);
-
- if(candidateSupported == false) {
- return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build());
- }
-
- final ListenableFuture<RpcResult<CompositeNode>> rpcResult = rpc.invokeRpc(
- NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
-
- final ListenableFuture<RpcResult<TransactionStatus>> transformed = Futures.transform(rpcResult,
- new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
- @Override
- public RpcResult<TransactionStatus> apply(final RpcResult<CompositeNode> input) {
- if (input.isSuccessful()) {
- return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
- } else {
- final RpcResultBuilder<TransactionStatus> 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<TransactionStatus> 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<ModifyAction> defaultOperation) throws ExecutionException {
- final CompositeNode editConfigRequest = createEditConfigRequest(editStructure, defaultOperation);
- final RpcResult<CompositeNode> 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<RpcResult<CompositeNode>> discardFuture = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
- Futures.addCallback(discardFuture, new FutureCallback<RpcResult<CompositeNode>>() {
- @Override
- public void onSuccess(final RpcResult<CompositeNode> 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<ModifyAction> operation,
- final Optional<CompositeNode> 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<PathArgument> 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<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
- builder.setQName(arg.getNodeType());
-
- addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
-
- builder.add(previous);
- previous = builder.toInstance();
- }
- return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
- }
-
- private void addPredicatesToCompositeNodeBuilder(final Map<QName, Object> predicates, final CompositeNodeBuilder<ImmutableCompositeNode> builder) {
- for (final Map.Entry<QName, Object> entry : predicates.entrySet()) {
- builder.addLeaf(entry.getKey(), entry.getValue());
- }
- }
-
- private Map<QName, Object> getPredicates(final YangInstanceIdentifier.PathArgument arg) {
- Map<QName, Object> predicates = Collections.emptyMap();
- if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
- predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues();
- }
- return predicates;
- }
-
- private CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional<ModifyAction> operation, final Optional<CompositeNode> lastChildOverride) {
- final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
- builder.setQName(arg.getNodeType());
-
- final Map<QName, Object> predicates = getPredicates(arg);
- addPredicatesToCompositeNodeBuilder(predicates, builder);
-
- if (operation.isPresent()) {
- builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
- }
- if (lastChildOverride.isPresent()) {
- final List<Node<?>> 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<ModifyAction> defaultOperation) {
- final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
-
- // Target
- final Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
- ret.add(targetWrapperNode);
-
- // Default operation
- if(defaultOperation.isPresent()) {
- final SimpleNode<String> 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.<Node<?>>of());
- } else {
- return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
- }
- }
-
- @Override
- public Object getIdentifier() {
- return this;
- }
-}
*/
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;
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<RpcResult<CompositeNode>> 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<RpcResult<CompositeNode>>() {
+ @Override
+ public void onSuccess(final RpcResult<CompositeNode> 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<Optional<NormalizedNode<?, ?>>, ReadFailedException> readConfigurationData(
final YangInstanceIdentifier path) {
- final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_GET_CONFIG_QNAME,
- NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, CONFIG_SOURCE_RUNNING, toFilterStructure(path)));
-
- final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(future, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
+ final ListenableFuture<RpcResult<CompositeNode>> configRunning = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path));
+ // Find data node and normalize its content
+ final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configRunning, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
@Override
public Optional<NormalizedNode<?, ?>> apply(final RpcResult<CompositeNode> result) {
checkReadSuccess(result, path);
private void checkReadSuccess(final RpcResult<CompositeNode> 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;
}
private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readOperationalData(
final YangInstanceIdentifier path) {
- final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_GET_QNAME, NetconfMessageTransformUtil.wrap(NETCONF_GET_QNAME, toFilterStructure(path)));
+ final ListenableFuture<RpcResult<CompositeNode>> configCandidate = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path));
- final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(future, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
+ // Find data node and normalize its content
+ final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configCandidate, new Function<RpcResult<CompositeNode>, Optional<NormalizedNode<?, ?>>>() {
@Override
public Optional<NormalizedNode<?, ?>> apply(final RpcResult<CompositeNode> result) {
checkReadSuccess(result, path);
throw new IllegalArgumentException(String.format("%s, Cannot read data %s for %s datastore, unknown datastore type", id, path, store));
}
- @Override public CheckedFuture<Boolean, ReadFailedException> exists(
- LogicalDatastoreType store,
- YangInstanceIdentifier path) {
- CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
+ @Override
+ public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
data = read(store, path);
try {
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;
}
}
@Override public CheckedFuture<Boolean, ReadFailedException> exists(
- LogicalDatastoreType store,
- YangInstanceIdentifier path) {
- CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
+ final LogicalDatastoreType store,
+ final YangInstanceIdentifier path) {
+ final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException>
data = read(store, path);
try {
--- /dev/null
+/*
+ * 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:
+ * <ul>
+ * <li>Running datastore is locked as the first thing and this lock has to succeed</li>
+ * </ul>
+ */
+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<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+ @Override
+ public ListenableFuture<RpcResult<CompositeNode>> 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));
+ }
+}
--- /dev/null
+/*
+ * 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:
+ * <ol>
+ * <li/> Lock candidate datastore on tx construction
+ * <ul>
+ * <li/> Lock has to succeed, if it does not, an attempt to discard changes is made
+ * <li/> Discard changes has to succeed
+ * <li/> If discard is successful, lock is reattempted
+ * <li/> Second lock attempt has to succeed
+ * </ul>
+ * <li/> Edit-config in candidate N times
+ * <ul>
+ * <li/> If any issue occurs during edit, datastore is discarded using discard-changes rpc, unlocked and an exception is thrown async
+ * </ul>
+ * <li/> Commit and Unlock candidate datastore async
+ * </ol>
+ */
+public class WriteCandidateTx extends AbstractWriteTx {
+
+ private static final Logger LOG = LoggerFactory.getLogger(WriteCandidateTx.class);
+
+ private static final Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>> RPC_RESULT_TO_TX_STATUS = new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
+ @Override
+ public RpcResult<TransactionStatus> apply(final RpcResult<CompositeNode> input) {
+ if (input.isSuccessful()) {
+ return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
+ } else {
+ final RpcResultBuilder<TransactionStatus> 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<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+ @Override
+ public ListenableFuture<RpcResult<CompositeNode>> 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<Void, TransactionCommitFailedException> submit() {
+ final ListenableFuture<Void> commitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
+ @Override
+ public Void apply(final RpcResult<TransactionStatus> input) {
+ return null;
+ }
+ });
+
+ return Futures.makeChecked(commitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
+ @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<RpcResult<TransactionStatus>> performCommit() {
+ final ListenableFuture<RpcResult<CompositeNode>> rpcResult = netOps.commit(new NetconfRpcFutureCallback("Commit", id) {
+ @Override
+ public void onSuccess(final RpcResult<CompositeNode> result) {
+ super.onSuccess(result);
+ LOG.debug("{}: Write successful, transaction: {}. Unlocking", id, getIdentifier());
+ cleanupOnSuccess();
+ }
+
+ @Override
+ protected void onUnsuccess(final RpcResult<CompositeNode> 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<ModifyAction> defaultOperation) throws NetconfDocumentedException {
+ invokeBlocking("Edit candidate", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+ @Override
+ public ListenableFuture<RpcResult<CompositeNode>> 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));
+ }
+
+}
--- /dev/null
+/*
+ * 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:
+ * <ol>
+ * <li/> Lock running datastore on tx construction
+ * <ul>
+ * <li/> Lock has to succeed, if it does not, transaction is failed
+ * </ul>
+ * <li/> Edit-config in running N times
+ * <ul>
+ * <li/> If any issue occurs during edit, datastore is unlocked and an exception is thrown
+ * </ul>
+ * <li/> Unlock running datastore on tx commit
+ * </ol>
+ */
+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<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+ @Override
+ public ListenableFuture<RpcResult<CompositeNode>> 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<Void, TransactionCommitFailedException> submit() {
+ final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
+ @Override
+ public Void apply(final RpcResult<TransactionStatus> input) {
+ return null;
+ }
+ });
+
+ return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
+ @Override
+ public TransactionCommitFailedException apply(final Exception input) {
+ return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
+ }
+ });
+ }
+
+ @Override
+ public synchronized ListenableFuture<RpcResult<TransactionStatus>> performCommit() {
+ unlock();
+ return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build());
+ }
+
+ @Override
+ protected void editConfig(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) throws NetconfDocumentedException {
+ invokeBlocking("Edit running", new Function<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+ @Override
+ public ListenableFuture<RpcResult<CompositeNode>> 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<NetconfBaseOps, ListenableFuture<RpcResult<CompositeNode>>>() {
+ @Override
+ public ListenableFuture<RpcResult<CompositeNode>> 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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<RpcResult<CompositeNode>> lock(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(datastore);
+
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(datastore));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> lockCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(NETCONF_CANDIDATE_QNAME));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+
+ public ListenableFuture<RpcResult<CompositeNode>> lockRunning(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_LOCK_QNAME, getLockContent(NETCONF_RUNNING_QNAME));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> unlock(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(datastore);
+
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(datastore));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> unlockRunning(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(NETCONF_RUNNING_QNAME));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> unlockCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_UNLOCK_QNAME, getUnLockContent(NETCONF_CANDIDATE_QNAME));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> discardChanges(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ Preconditions.checkNotNull(callback);
+
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> commit(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ Preconditions.checkNotNull(callback);
+
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> validate(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(datastore);
+
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_VALIDATE_QNAME, getValidateContent(datastore));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> validateCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ return validate(callback, NETCONF_CANDIDATE_QNAME);
+ }
+
+
+ public ListenableFuture<RpcResult<CompositeNode>> validateRunning(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ return validate(callback, NETCONF_RUNNING_QNAME);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> copyConfig(final FutureCallback<RpcResult<CompositeNode>> callback, final QName source, final QName target) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(source);
+ Preconditions.checkNotNull(target);
+
+ final ListenableFuture<RpcResult<CompositeNode>> future = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COPY_CONFIG_QNAME, getCopyConfigContent(source, target));
+ Futures.addCallback(future, callback);
+ return future;
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> copyRunningToCandidate(final FutureCallback<RpcResult<CompositeNode>> callback) {
+ return copyConfig(callback, NETCONF_RUNNING_QNAME, NETCONF_CANDIDATE_QNAME);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> getConfig(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore, final Optional<YangInstanceIdentifier> filterPath) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(datastore);
+
+ final ListenableFuture<RpcResult<CompositeNode>> 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<RpcResult<CompositeNode>> getConfigRunning(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+ return getConfig(callback, NETCONF_RUNNING_QNAME, filterPath);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> getConfigCandidate(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+ return getConfig(callback, NETCONF_CANDIDATE_QNAME, filterPath);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> get(final FutureCallback<RpcResult<CompositeNode>> callback, final QName datastore, final Optional<YangInstanceIdentifier> filterPath) {
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(datastore);
+
+ final ListenableFuture<RpcResult<CompositeNode>> 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<RpcResult<CompositeNode>> getRunning(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+ return get(callback, NETCONF_RUNNING_QNAME, filterPath);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> getCandidate(final FutureCallback<RpcResult<CompositeNode>> callback, final Optional<YangInstanceIdentifier> filterPath) {
+ return get(callback, NETCONF_CANDIDATE_QNAME, filterPath);
+ }
+
+
+ public ListenableFuture<RpcResult<CompositeNode>> editConfigCandidate(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) {
+ return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.of(modifyAction), rollback);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> editConfigCandidate(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final boolean rollback) {
+ return editConfig(callback, NETCONF_CANDIDATE_QNAME, editStructure, Optional.<ModifyAction>absent(), rollback);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> editConfigRunning(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final ModifyAction modifyAction, final boolean rollback) {
+ return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.of(modifyAction), rollback);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> editConfigRunning(final FutureCallback<? super RpcResult<CompositeNode>> callback, final CompositeNode editStructure, final boolean rollback) {
+ return editConfig(callback, NETCONF_RUNNING_QNAME, editStructure, Optional.<ModifyAction>absent(), rollback);
+ }
+
+ public ListenableFuture<RpcResult<CompositeNode>> editConfig(final FutureCallback<? super RpcResult<CompositeNode>> callback, final QName datastore, final CompositeNode editStructure, final Optional<ModifyAction> modifyAction, final boolean rollback) {
+ Preconditions.checkNotNull(editStructure);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(datastore);
+
+ final ListenableFuture<RpcResult<CompositeNode>> 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<ModifyAction> defaultOperation, final boolean rollback) {
+ final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
+
+ // Target
+ ret.add(getTargetNode(datastore));
+
+ // Default operation
+ if(defaultOperation.isPresent()) {
+ final SimpleNode<String> 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.<Node<?>> singletonList(new SimpleNodeTOImpl<>(datastore, null, null)));
+ }
+
+
+ public static CompositeNode getLockContent(final QName datastore) {
+ return NodeFactory.createImmutableCompositeNode(NETCONF_LOCK_QNAME, null, Collections.<Node<?>>singletonList(
+ getTargetNode(datastore)));
+ }
+
+ private static CompositeNode getTargetNode(final QName datastore) {
+ return NodeFactory.createImmutableCompositeNode(NETCONF_TARGET_QNAME, null, Collections.<Node<?>>singletonList(
+ NodeFactory.createImmutableSimpleNode(datastore, null, null)
+ ));
+ }
+
+ public static CompositeNode getCopyConfigContent(final QName source, final QName target) {
+ return NodeFactory.createImmutableCompositeNode(NETCONF_LOCK_QNAME, null,
+ Lists.<Node<?>> newArrayList(getTargetNode(target), getSourceNode(source)));
+ }
+
+ public static CompositeNode getValidateContent(final QName source) {
+ return NodeFactory.createImmutableCompositeNode(NETCONF_VALIDATE_QNAME, null, Lists.<Node<?>> newArrayList(getSourceNode(source)));
+ }
+
+ public static CompositeNode getUnLockContent(final QName preferedDatastore) {
+ return NodeFactory.createImmutableCompositeNode(NETCONF_UNLOCK_QNAME, null, Collections.<Node<?>>singletonList(
+ getTargetNode(preferedDatastore)));
+ }
+
+}
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;
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;
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<Node<?>> RUNNING = Collections.<Node<?>> 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.<Node<?>>emptyList());
static Node<?> toNode(final YangInstanceIdentifier.NodeIdentifierWithPredicates argument, final Node<?> node) {
final List<Node<?>> list = new ArrayList<>();
for (final Map.Entry<QName, Object> 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);
}
return current;
}
+
+ public static String modifyOperationToXmlString(final ModifyAction operation) {
+ return operation.name().toLowerCase();
+ }
+
+
+ public static CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation,
+ final Optional<CompositeNode> lastChildOverride) {
+ Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath);
+
+ List<YangInstanceIdentifier.PathArgument> 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<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
+ builder.setQName(arg.getNodeType());
+
+ addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
+
+ builder.add(previous);
+ previous = builder.toInstance();
+ }
+ return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
+ }
+
+ public static void addPredicatesToCompositeNodeBuilder(final Map<QName, Object> predicates, final CompositeNodeBuilder<ImmutableCompositeNode> builder) {
+ for (final Map.Entry<QName, Object> entry : predicates.entrySet()) {
+ builder.addLeaf(entry.getKey(), entry.getValue());
+ }
+ }
+
+ public static Map<QName, Object> getPredicates(final YangInstanceIdentifier.PathArgument arg) {
+ Map<QName, Object> 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<ModifyAction> operation, final Optional<CompositeNode> lastChildOverride) {
+ final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
+ builder.setQName(arg.getNodeType());
+
+ final Map<QName, Object> predicates = getPredicates(arg);
+ addPredicatesToCompositeNodeBuilder(predicates, builder);
+
+ if (operation.isPresent()) {
+ builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
+ }
+ if (lastChildOverride.isPresent()) {
+ final List<Node<?>> children = lastChildOverride.get().getValue();
+ for(final Node<?> child : children) {
+ if(!predicates.containsKey(child.getKey())) {
+ builder.add(child);
+ }
+ }
+ }
+
+ return builder.toInstance();
+ }
}
--- /dev/null
+/*
+ * 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<RpcResult<CompositeNode>> {
+ 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<CompositeNode> result) {
+ if(result.isSuccessful()) {
+ LOG.trace("{}: " + type + " invoked successfully", id);
+ } else {
+ onUnsuccess(result);
+ }
+ }
+
+ protected void onUnsuccess(final RpcResult<CompositeNode> result) {
+ LOG.warn("{}: " + type + " invoked unsuccessfully: {}", id, result.getErrors());
+ }
+
+ @Override
+ public void onFailure(final Throwable t) {
+ LOG.warn("{}: " + type + " failed.", id, t);
+ }
+}
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;
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- doReturn(Futures.<RpcResult<CompositeNode>>immediateFailedFuture(new IllegalStateException("Failed tx")))
- .doReturn(Futures.immediateFuture(RpcResultBuilder.<CompositeNode>success().build()))
+ ListenableFuture<RpcResult<CompositeNode>> successFuture = Futures.immediateFuture(RpcResultBuilder.<CompositeNode>success().build());
+
+ doReturn(successFuture)
+ .doReturn(Futures.<RpcResult<CompositeNode>>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();
}
@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.<String>emptySet()));
final CheckedFuture<Void, TransactionCommitFailedException> 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.<CompositeNode>success().build()))
.doReturn(Futures.<RpcResult<CompositeNode>>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.<String>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");
}
}
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);
}
}
});