Just in case rollback-on-error capability is supported
+ Add error response warning to netconf-connector
Change-Id: I16251a12b270bd0ed89b39bfccc5251b4b3fe934
Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
NetconfDeviceListener listener;
NetconfDeviceListener listener;
+ private boolean rollbackSupported;
+
+
public NetconfDevice(String name) {
this.name = name;
this.logger = LoggerFactory.getLogger(NetconfDevice.class + "#" + name);
public NetconfDevice(String name) {
this.name = name;
this.logger = LoggerFactory.getLogger(NetconfDevice.class + "#" + name);
- void bringUp(final SchemaSourceProvider<String> delegate, final Set<QName> capabilities) {
+ void bringUp(final SchemaSourceProvider<String> delegate, final Set<QName> capabilities, final boolean rollbackSupported) {
// This has to be called from separate thread, not from netty thread calling onSessionUp in DeviceListener.
// Reason: delegate.getSchema blocks thread when waiting for response
// however, if the netty thread is blocked, no incoming message can be processed
// This has to be called from separate thread, not from netty thread calling onSessionUp in DeviceListener.
// Reason: delegate.getSchema blocks thread when waiting for response
// however, if the netty thread is blocked, no incoming message can be processed
processingExecutor.submit(new Runnable() {
@Override
public void run() {
processingExecutor.submit(new Runnable() {
@Override
public void run() {
+ NetconfDevice.this.rollbackSupported = rollbackSupported;
remoteSourceProvider = schemaSourceProvider.createInstanceFor(delegate);
deviceContextProvider = new NetconfDeviceSchemaContextProvider(NetconfDevice.this, remoteSourceProvider);
deviceContextProvider.createContextFromCapabilities(capabilities);
remoteSourceProvider = schemaSourceProvider.createInstanceFor(delegate);
deviceContextProvider = new NetconfDeviceSchemaContextProvider(NetconfDevice.this, remoteSourceProvider);
deviceContextProvider.createContextFromCapabilities(capabilities);
public DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
DataModification<InstanceIdentifier, CompositeNode> modification) {
NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit = new NetconfDeviceTwoPhaseCommitTransaction(this,
public DataCommitTransaction<InstanceIdentifier, CompositeNode> requestCommit(
DataModification<InstanceIdentifier, CompositeNode> modification) {
NetconfDeviceTwoPhaseCommitTransaction twoPhaseCommit = new NetconfDeviceTwoPhaseCommitTransaction(this,
+ modification, true, rollbackSupported);
try {
twoPhaseCommit.prepare();
} catch (InterruptedException e) {
try {
twoPhaseCommit.prepare();
} catch (InterruptedException e) {
*/
package org.opendaylight.controller.sal.connect.netconf;
*/
package org.opendaylight.controller.sal.connect.netconf;
+import com.google.common.collect.Sets;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import com.google.common.util.concurrent.ListenableFuture;
class NetconfDeviceListener implements NetconfClientSessionListener {
import com.google.common.util.concurrent.ListenableFuture;
class NetconfDeviceListener implements NetconfClientSessionListener {
private static final class Request {
final UncancellableFuture<RpcResult<CompositeNode>> future;
final NetconfMessage request;
private static final class Request {
final UncancellableFuture<RpcResult<CompositeNode>> future;
final NetconfMessage request;
delegate = SchemaSourceProviders.noopProvider();
}
delegate = SchemaSourceProviders.noopProvider();
}
- device.bringUp(delegate, caps);
+ device.bringUp(delegate, caps, isRollbackSupported(session.getServerCapabilities()));
+
+ }
+
+ private static boolean isRollbackSupported(final Collection<String> serverCapabilities) {
+ // TODO rollback capability cannot be searched for in Set<QName> caps
+ // since this set does not contain module-less capabilities
+ return Sets.newHashSet(serverCapabilities).contains(NetconfMapping.NETCONF_ROLLBACK_ON_ERROR_URI.toString());
}
private synchronized void tearDown(final Exception e) {
}
private synchronized void tearDown(final Exception e) {
requests.poll();
LOG.debug("Matched {} to {}", r.request, message);
requests.poll();
LOG.debug("Matched {} to {}", r.request, message);
- // FIXME: this can throw exceptions, which should result
- // in the future failing
- NetconfMapping.checkValidReply(r.request, message);
+ try {
+ NetconfMapping.checkValidReply(r.request, message);
+ } catch (IllegalStateException e) {
+ LOG.warn("Invalid request-reply match, reply message contains different message-id", e);
+ r.future.setException(e);
+ return;
+ }
+
+ try {
+ NetconfMapping.checkSuccessReply(message);
+ } catch (IllegalStateException e) {
+ LOG.warn("Error reply from remote device", e);
+ r.future.setException(e);
+ return;
+ }
+
r.future.set(Rpcs.getRpcResult(true, NetconfMapping.toNotificationNode(message, device.getSchemaContext()),
Collections.<RpcError>emptyList()));
} else {
r.future.set(Rpcs.getRpcResult(true, NetconfMapping.toNotificationNode(message, device.getSchemaContext()),
Collections.<RpcError>emptyList()));
} else {
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_COMMIT_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_CONFIG_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_EDIT_CONFIG_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_COMMIT_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_CONFIG_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_EDIT_CONFIG_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_ERROR_OPTION_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_OPERATION_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_RUNNING_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_TARGET_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_OPERATION_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_RUNNING_QNAME;
import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.NETCONF_TARGET_QNAME;
+import static org.opendaylight.controller.sal.connect.netconf.NetconfMapping.ROLLBACK_ON_ERROR_OPTION;
import java.util.Collection;
import java.util.Collections;
import java.util.Collection;
import java.util.Collections;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.Node;
import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.Node;
import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.impl.SimpleNodeTOImpl;
import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final DataModification<InstanceIdentifier, CompositeNode> modification;
private final NetconfDevice device;
private final boolean candidateSupported;
private final DataModification<InstanceIdentifier, CompositeNode> modification;
private final NetconfDevice device;
private final boolean candidateSupported;
+ private final boolean rollbackSupported;
public NetconfDeviceTwoPhaseCommitTransaction(NetconfDevice device,
DataModification<InstanceIdentifier, CompositeNode> modification,
public NetconfDeviceTwoPhaseCommitTransaction(NetconfDevice device,
DataModification<InstanceIdentifier, CompositeNode> modification,
- boolean candidateSupported) {
+ boolean candidateSupported, boolean rollbackOnErrorSupported) {
this.device = Preconditions.checkNotNull(device);
this.modification = Preconditions.checkNotNull(modification);
this.candidateSupported = candidateSupported;
this.device = Preconditions.checkNotNull(device);
this.modification = Preconditions.checkNotNull(modification);
this.candidateSupported = candidateSupported;
+ this.rollbackSupported = rollbackOnErrorSupported;
}
void prepare() throws InterruptedException, ExecutionException {
}
void prepare() throws InterruptedException, ExecutionException {
} else {
targetNode = ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
}
} else {
targetNode = ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
}
Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
+
+ if(rollbackSupported) {
+ LOG.debug("Rollback-on-error supported, setting {} to {}", NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
+ ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
+ }
+
ret.add(targetWrapperNode);
return ret;
}
ret.add(targetWrapperNode);
return ret;
}
import javax.annotation.Nullable;
import org.opendaylight.controller.netconf.api.NetconfMessage;
import javax.annotation.Nullable;
import org.opendaylight.controller.netconf.api.NetconfMessage;
+import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
+import org.opendaylight.controller.netconf.util.xml.XmlUtil;
import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.RpcError;
import org.opendaylight.controller.sal.common.util.Rpcs;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.RpcError;
public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
public static String NETCONF_MONITORING_URI = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
public static URI NETCONF_NOTIFICATION_URI = URI.create("urn:ietf:params:xml:ns:netconf:notification:1.0");
public static URI NETCONF_URI = URI.create("urn:ietf:params:xml:ns:netconf:base:1.0");
public static String NETCONF_MONITORING_URI = "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring";
public static URI NETCONF_NOTIFICATION_URI = URI.create("urn:ietf:params:xml:ns:netconf:notification:1.0");
+ public static URI NETCONF_ROLLBACK_ON_ERROR_URI = URI.create("urn:ietf:params:netconf:capability:rollback-on-error:1.0");
public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf");
public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc");
public static QName NETCONF_QNAME = QName.create(NETCONF_URI, null, "netconf");
public static QName NETCONF_RPC_QNAME = QName.create(NETCONF_QNAME, "rpc");
public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate");
public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running");
public static QName NETCONF_CANDIDATE_QNAME = QName.create(NETCONF_QNAME, "candidate");
public static QName NETCONF_RUNNING_QNAME = QName.create(NETCONF_QNAME, "running");
+ public static QName NETCONF_ERROR_OPTION_QNAME = QName.create(NETCONF_QNAME, "error-option");
+ public static String ROLLBACK_ON_ERROR_OPTION = "rollback-on-error";
+
public static QName NETCONF_RPC_REPLY_QNAME = QName.create(NETCONF_QNAME, "rpc-reply");
public static QName NETCONF_OK_QNAME = QName.create(NETCONF_QNAME, "ok");
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_OK_QNAME = QName.create(NETCONF_QNAME, "ok");
public static QName NETCONF_DATA_QNAME = QName.create(NETCONF_QNAME, "data");
public static void checkValidReply(NetconfMessage input, NetconfMessage output) {
String inputMsgId = input.getDocument().getDocumentElement().getAttribute("message-id");
String outputMsgId = output.getDocument().getDocumentElement().getAttribute("message-id");
public static void checkValidReply(NetconfMessage input, NetconfMessage output) {
String inputMsgId = input.getDocument().getDocumentElement().getAttribute("message-id");
String outputMsgId = output.getDocument().getDocumentElement().getAttribute("message-id");
- Preconditions.checkState(inputMsgId.equals(outputMsgId), "Rpc request and reply message IDs must be same.");
+
+ if(inputMsgId.equals(outputMsgId) == false) {
+ String requestXml = XmlUtil.toString(input.getDocument());
+ String responseXml = XmlUtil.toString(output.getDocument());
+ throw new IllegalStateException(String.format("Rpc request and reply message IDs must be same. Request: %s, response: %s", requestXml, responseXml));
+ }
+ public static void checkSuccessReply(NetconfMessage output) {
+ if(NetconfMessageUtil.isErrorMessage(output)) {
+ throw new IllegalStateException(String.format("Response contains error: %s", XmlUtil.toString(output.getDocument())));
+ }
+ }