2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html.
9 package org.opendaylight.controller.sal.connect.netconf.sal.tx;
11 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.DISCARD_CHANGES_RPC_CONTENT;
12 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
13 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME;
14 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME;
15 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DISCARD_CHANGES_QNAME;
16 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
17 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME;
18 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
19 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME;
20 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME;
21 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION;
23 import com.google.common.base.Function;
24 import com.google.common.base.Optional;
25 import com.google.common.base.Preconditions;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.collect.Iterables;
28 import com.google.common.collect.Lists;
29 import com.google.common.util.concurrent.CheckedFuture;
30 import com.google.common.util.concurrent.FutureCallback;
31 import com.google.common.util.concurrent.Futures;
32 import com.google.common.util.concurrent.ListenableFuture;
33 import java.util.Collections;
34 import java.util.List;
36 import java.util.concurrent.ExecutionException;
37 import java.util.concurrent.atomic.AtomicBoolean;
38 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
39 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
40 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
41 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
42 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
43 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
44 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
45 import org.opendaylight.controller.sal.core.api.RpcImplementation;
46 import org.opendaylight.yangtools.yang.common.QName;
47 import org.opendaylight.yangtools.yang.common.RpcError;
48 import org.opendaylight.yangtools.yang.common.RpcResult;
49 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
50 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
51 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
53 import org.opendaylight.yangtools.yang.data.api.Node;
54 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
56 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
57 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
58 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
62 public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction, FutureCallback<RpcResult<TransactionStatus>> {
64 private static final Logger LOG = LoggerFactory.getLogger(NetconfDeviceWriteOnlyTx.class);
66 private final RemoteDeviceId id;
67 private final RpcImplementation rpc;
68 private final DataNormalizer normalizer;
70 private final boolean rollbackSupported;
71 private final boolean candidateSupported;
72 private final CompositeNode targetNode;
74 // Allow commit to be called only once
75 private final AtomicBoolean finished = new AtomicBoolean(false);
77 public NetconfDeviceWriteOnlyTx(final RemoteDeviceId id, final RpcImplementation rpc, final DataNormalizer normalizer, final boolean candidateSupported, final boolean rollbackOnErrorSupported) {
80 this.normalizer = normalizer;
82 this.candidateSupported = candidateSupported;
83 this.targetNode = getTargetNode(this.candidateSupported);
84 this.rollbackSupported = rollbackOnErrorSupported;
88 public boolean cancel() {
93 return discardChanges();
96 private boolean isFinished() {
97 return finished.get();
100 private boolean discardChanges() {
103 if(candidateSupported) {
104 sendDiscardChanges();
109 // TODO should the edit operations be blocking ?
110 // TODO should the discard-changes operations be blocking ?
113 public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
115 Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store);
118 final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id);
119 final CompositeNode legacyData = normalizer.toLegacy(path, data);
121 createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE));
122 } catch (final ExecutionException e) {
123 LOG.warn("{}: Error putting data to {}, data: {}, discarding changes", id, path, data, e);
125 throw new RuntimeException(id + ": Error while replacing " + path, e);
129 private void checkNotFinished() {
130 Preconditions.checkState(isFinished() == false, "%s: Transaction %s already finished", id, getIdentifier());
134 public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
136 Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store);
139 final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id);
140 final CompositeNode legacyData = normalizer.toLegacy(path, data);
142 createEditConfigStructure(legacyPath, Optional.<ModifyAction> absent(), Optional.fromNullable(legacyData)), Optional.<ModifyAction> absent());
143 } catch (final ExecutionException e) {
144 LOG.warn("{}: Error merging data to {}, data: {}, discarding changes", id, path, data, e);
146 throw new RuntimeException(id + ": Error while merging " + path, e);
151 public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
153 Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store);
157 createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id), Optional.of(ModifyAction.DELETE), Optional.<CompositeNode>absent()), Optional.of(ModifyAction.NONE));
158 } catch (final ExecutionException e) {
159 LOG.warn("{}: Error deleting data {}, discarding changes", id, path, e);
161 throw new RuntimeException(id + ": Error while deleting " + path, e);
166 public CheckedFuture<Void, TransactionCommitFailedException> submit() {
167 final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
169 public Void apply(final RpcResult<TransactionStatus> input) {
174 return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
176 public TransactionCommitFailedException apply(final Exception input) {
177 return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
183 public ListenableFuture<RpcResult<TransactionStatus>> commit() {
187 if(candidateSupported == false) {
188 return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build());
191 final ListenableFuture<RpcResult<CompositeNode>> rpcResult = rpc.invokeRpc(
192 NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
194 final ListenableFuture<RpcResult<TransactionStatus>> transformed = Futures.transform(rpcResult,
195 new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
197 public RpcResult<TransactionStatus> apply(final RpcResult<CompositeNode> input) {
198 if (input.isSuccessful()) {
199 return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
201 final RpcResultBuilder<TransactionStatus> failed = RpcResultBuilder.failed();
202 for (final RpcError rpcError : input.getErrors()) {
203 failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(),
204 rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause());
206 return failed.build();
211 Futures.addCallback(transformed, this);
216 public void onSuccess(final RpcResult<TransactionStatus> result) {
217 LOG.debug("{}: Write successful, transaction: {}", id, getIdentifier());
221 public void onFailure(final Throwable t) {
222 LOG.warn("{}: Write failed, transaction {}, discarding changes", id, getIdentifier(), t);
226 private void sendEditRpc(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) throws ExecutionException {
227 final CompositeNode editConfigRequest = createEditConfigRequest(editStructure, defaultOperation);
228 final RpcResult<CompositeNode> rpcResult;
230 rpcResult = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, editConfigRequest).get();
231 } catch (final InterruptedException e) {
232 Thread.currentThread().interrupt();
233 throw new RuntimeException(id + ": Interrupted while waiting for response", e);
237 if(rpcResult.isSuccessful() == false) {
238 throw new ExecutionException(
239 String.format("%s: Pre-commit rpc failed, request: %s, errors: %s", id, editConfigRequest, rpcResult.getErrors()), null);
243 private void sendDiscardChanges() {
244 final ListenableFuture<RpcResult<CompositeNode>> discardFuture = rpc.invokeRpc(NETCONF_DISCARD_CHANGES_QNAME, DISCARD_CHANGES_RPC_CONTENT);
245 Futures.addCallback(discardFuture, new FutureCallback<RpcResult<CompositeNode>>() {
247 public void onSuccess(final RpcResult<CompositeNode> result) {
248 LOG.debug("{}: Discarding transaction: {}", id, getIdentifier());
252 public void onFailure(final Throwable t) {
253 LOG.error("{}: Discarding changes failed, transaction: {}. Device configuration might be corrupted", id, getIdentifier(), t);
254 throw new RuntimeException(id + ": Discarding changes failed, transaction " + getIdentifier(), t);
259 private CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation,
260 final Optional<CompositeNode> lastChildOverride) {
261 Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath);
263 List<YangInstanceIdentifier.PathArgument> reversedPath = Lists.reverse(dataPath.getPath());
265 // Create deepest edit element with expected edit operation
266 CompositeNode previous = getDeepestEditElement(reversedPath.get(0), operation, lastChildOverride);
268 // Remove already processed deepest child
269 reversedPath = Lists.newArrayList(reversedPath);
270 reversedPath.remove(0);
272 // Create edit structure in reversed order
273 for (final YangInstanceIdentifier.PathArgument arg : reversedPath) {
274 final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
275 builder.setQName(arg.getNodeType());
277 addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
279 builder.add(previous);
280 previous = builder.toInstance();
282 return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
285 private void addPredicatesToCompositeNodeBuilder(final Map<QName, Object> predicates, final CompositeNodeBuilder<ImmutableCompositeNode> builder) {
286 for (final Map.Entry<QName, Object> entry : predicates.entrySet()) {
287 builder.addLeaf(entry.getKey(), entry.getValue());
291 private Map<QName, Object> getPredicates(final YangInstanceIdentifier.PathArgument arg) {
292 Map<QName, Object> predicates = Collections.emptyMap();
293 if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
294 predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues();
299 private CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional<ModifyAction> operation, final Optional<CompositeNode> lastChildOverride) {
300 final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
301 builder.setQName(arg.getNodeType());
303 final Map<QName, Object> predicates = getPredicates(arg);
304 addPredicatesToCompositeNodeBuilder(predicates, builder);
306 if (operation.isPresent()) {
307 builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
309 if (lastChildOverride.isPresent()) {
310 final List<Node<?>> children = lastChildOverride.get().getValue();
311 for(final Node<?> child : children) {
312 if(!predicates.containsKey(child.getKey())) {
318 return builder.toInstance();
321 private CompositeNode createEditConfigRequest(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) {
322 final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
325 final Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
326 ret.add(targetWrapperNode);
329 if(defaultOperation.isPresent()) {
330 final SimpleNode<String> defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, modifyOperationToXmlString(defaultOperation.get()));
335 if(rollbackSupported) {
336 ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
339 ret.setQName(NETCONF_EDIT_CONFIG_QNAME);
341 ret.add(editStructure);
342 return ret.toInstance();
345 private String modifyOperationToXmlString(final ModifyAction operation) {
346 return operation.name().toLowerCase();
349 public CompositeNode getTargetNode(final boolean candidateSupported) {
350 if(candidateSupported) {
351 return ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.<Node<?>>of());
353 return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
358 public Object getIdentifier() {