Merge "BUG 1082 Migrate sal-rest-connector to Async Data Broker API"
[controller.git] / opendaylight / md-sal / sal-netconf-connector / src / main / java / org / opendaylight / controller / sal / connect / netconf / sal / tx / NetconfDeviceWriteOnlyTx.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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.
7  */
8
9 package org.opendaylight.controller.sal.connect.netconf.sal.tx;
10
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;
22
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;
35 import java.util.Map;
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;
61
62 public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction, FutureCallback<RpcResult<TransactionStatus>> {
63
64     private static final Logger LOG  = LoggerFactory.getLogger(NetconfDeviceWriteOnlyTx.class);
65
66     private final RemoteDeviceId id;
67     private final RpcImplementation rpc;
68     private final DataNormalizer normalizer;
69
70     private final boolean rollbackSupported;
71     private final boolean candidateSupported;
72     private final CompositeNode targetNode;
73
74     // Allow commit to be called only once
75     private final AtomicBoolean finished = new AtomicBoolean(false);
76
77     public NetconfDeviceWriteOnlyTx(final RemoteDeviceId id, final RpcImplementation rpc, final DataNormalizer normalizer, final boolean candidateSupported, final boolean rollbackOnErrorSupported) {
78         this.id = id;
79         this.rpc = rpc;
80         this.normalizer = normalizer;
81
82         this.candidateSupported = candidateSupported;
83         this.targetNode = getTargetNode(this.candidateSupported);
84         this.rollbackSupported = rollbackOnErrorSupported;
85     }
86
87     @Override
88     public boolean cancel() {
89         if(isFinished()) {
90             return false;
91         }
92
93         return discardChanges();
94     }
95
96     private boolean isFinished() {
97         return finished.get();
98     }
99
100     private boolean discardChanges() {
101         finished.set(true);
102
103         if(candidateSupported) {
104             sendDiscardChanges();
105         }
106         return true;
107     }
108
109     // TODO should the edit operations be blocking ?
110     // TODO should the discard-changes operations be blocking ?
111
112     @Override
113     public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
114         checkNotFinished();
115         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store);
116
117         try {
118             final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id);
119             final CompositeNode legacyData = normalizer.toLegacy(path, data);
120             sendEditRpc(
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);
124             discardChanges();
125             throw new RuntimeException(id + ": Error while replacing " + path, e);
126         }
127     }
128
129     private void checkNotFinished() {
130         Preconditions.checkState(isFinished() == false, "%s: Transaction %s already finished", id, getIdentifier());
131     }
132
133     @Override
134     public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
135         checkNotFinished();
136         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store);
137
138         try {
139             final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path, id);
140             final CompositeNode legacyData = normalizer.toLegacy(path, data);
141             sendEditRpc(
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);
145             discardChanges();
146             throw new RuntimeException(id + ": Error while merging " + path, e);
147         }
148     }
149
150     @Override
151     public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
152         checkNotFinished();
153         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "%s: Can merge only configuration, not %s", id, store);
154
155         try {
156             sendEditRpc(
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);
160             discardChanges();
161             throw new RuntimeException(id + ": Error while deleting " + path, e);
162         }
163     }
164
165     @Override
166     public CheckedFuture<Void, TransactionCommitFailedException> submit() {
167         final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
168             @Override
169             public Void apply(final RpcResult<TransactionStatus> input) {
170                 return null;
171             }
172         });
173
174         return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
175             @Override
176             public TransactionCommitFailedException apply(final Exception input) {
177                 return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
178             }
179         });
180     }
181
182     @Override
183     public ListenableFuture<RpcResult<TransactionStatus>> commit() {
184         checkNotFinished();
185         finished.set(true);
186
187         if(candidateSupported == false) {
188             return Futures.immediateFuture(RpcResultBuilder.success(TransactionStatus.COMMITED).build());
189         }
190
191         final ListenableFuture<RpcResult<CompositeNode>> rpcResult = rpc.invokeRpc(
192                 NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, NetconfMessageTransformUtil.COMMIT_RPC_CONTENT);
193
194         final ListenableFuture<RpcResult<TransactionStatus>> transformed = Futures.transform(rpcResult,
195                 new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
196                     @Override
197                     public RpcResult<TransactionStatus> apply(final RpcResult<CompositeNode> input) {
198                         if (input.isSuccessful()) {
199                             return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
200                         } else {
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());
205                             }
206                             return failed.build();
207                         }
208                     }
209                 });
210
211         Futures.addCallback(transformed, this);
212         return transformed;
213     }
214
215     @Override
216     public void onSuccess(final RpcResult<TransactionStatus> result) {
217         LOG.debug("{}: Write successful, transaction: {}", id, getIdentifier());
218     }
219
220     @Override
221     public void onFailure(final Throwable t) {
222         LOG.warn("{}: Write failed, transaction {}, discarding changes", id, getIdentifier(), t);
223         discardChanges();
224     }
225
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;
229         try {
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);
234         }
235
236         // Check result
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);
240         }
241     }
242
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>>() {
246             @Override
247             public void onSuccess(final RpcResult<CompositeNode> result) {
248                 LOG.debug("{}: Discarding transaction: {}", id, getIdentifier());
249             }
250
251             @Override
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);
255             }
256         });
257     }
258
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);
262
263         List<YangInstanceIdentifier.PathArgument> reversedPath = Lists.reverse(dataPath.getPath());
264
265         // Create deepest edit element with expected edit operation
266         CompositeNode previous = getDeepestEditElement(reversedPath.get(0), operation, lastChildOverride);
267
268         // Remove already processed deepest child
269         reversedPath = Lists.newArrayList(reversedPath);
270         reversedPath.remove(0);
271
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());
276
277             addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
278
279             builder.add(previous);
280             previous = builder.toInstance();
281         }
282         return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
283     }
284
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());
288         }
289     }
290
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();
295         }
296         return predicates;
297     }
298
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());
302
303         final Map<QName, Object> predicates = getPredicates(arg);
304         addPredicatesToCompositeNodeBuilder(predicates, builder);
305
306         if (operation.isPresent()) {
307             builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
308         }
309         if (lastChildOverride.isPresent()) {
310             final List<Node<?>> children = lastChildOverride.get().getValue();
311             for(final Node<?> child : children) {
312                 if(!predicates.containsKey(child.getKey())) {
313                     builder.add(child);
314                 }
315             }
316         }
317
318         return builder.toInstance();
319     }
320
321     private CompositeNode createEditConfigRequest(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) {
322         final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
323
324         // Target
325         final Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
326         ret.add(targetWrapperNode);
327
328         // Default operation
329         if(defaultOperation.isPresent()) {
330             final SimpleNode<String> defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, modifyOperationToXmlString(defaultOperation.get()));
331             ret.add(defOp);
332         }
333
334         // Error option
335         if(rollbackSupported) {
336             ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
337         }
338
339         ret.setQName(NETCONF_EDIT_CONFIG_QNAME);
340         // Edit content
341         ret.add(editStructure);
342         return ret.toInstance();
343     }
344
345     private String modifyOperationToXmlString(final ModifyAction operation) {
346         return operation.name().toLowerCase();
347     }
348
349     public CompositeNode getTargetNode(final boolean candidateSupported) {
350         if(candidateSupported) {
351             return ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.<Node<?>>of());
352         } else {
353             return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
354         }
355     }
356
357     @Override
358     public Object getIdentifier() {
359         return this;
360     }
361 }