fa94ad0b45bbbdc30809b08f0ffe4101f00bbdfb
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / sal / tx / AbstractWriteTx.java
1 /*
2  * Copyright (c) 2014, 2015 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 package org.opendaylight.netconf.sal.connect.netconf.sal.tx;
9
10 import com.google.common.base.Preconditions;
11 import com.google.common.util.concurrent.FluentFuture;
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.Futures;
14 import com.google.common.util.concurrent.ListenableFuture;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import com.google.common.util.concurrent.SettableFuture;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.concurrent.CopyOnWriteArrayList;
22 import javax.annotation.Nonnull;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.opendaylight.mdsal.common.api.CommitInfo;
25 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
26 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
28 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
29 import org.opendaylight.netconf.api.DocumentedException;
30 import org.opendaylight.netconf.api.NetconfDocumentedException;
31 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
32 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
33 import org.opendaylight.yangtools.yang.common.RpcError;
34 import org.opendaylight.yangtools.yang.common.RpcResult;
35 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
36 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
38 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
39 import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 public abstract class AbstractWriteTx implements DOMDataTreeWriteTransaction {
45
46     private static final Logger LOG  = LoggerFactory.getLogger(AbstractWriteTx.class);
47
48     protected final RemoteDeviceId id;
49     protected final NetconfBaseOps netOps;
50     protected final boolean rollbackSupport;
51     protected final List<ListenableFuture<DOMRpcResult>> resultsFutures = new ArrayList<>();
52     private final List<TxListener> listeners = new CopyOnWriteArrayList<>();
53     // Allow commit to be called only once
54     protected volatile boolean finished = false;
55
56     public AbstractWriteTx(final NetconfBaseOps netOps, final RemoteDeviceId id, final boolean rollbackSupport) {
57         this.netOps = netOps;
58         this.id = id;
59         this.rollbackSupport = rollbackSupport;
60         init();
61     }
62
63     protected static boolean isSuccess(final DOMRpcResult result) {
64         return result.getErrors().isEmpty();
65     }
66
67     protected void checkNotFinished() {
68         Preconditions.checkState(!isFinished(), "%s: Transaction %s already finished", id, getIdentifier());
69     }
70
71     protected boolean isFinished() {
72         return finished;
73     }
74
75     @Override
76     public synchronized boolean cancel() {
77         if (isFinished()) {
78             return false;
79         }
80         listeners.forEach(listener -> listener.onTransactionCancelled(this));
81         finished = true;
82         cleanup();
83         return true;
84     }
85
86     protected abstract void init();
87
88     protected abstract void cleanup();
89
90     @Override
91     public Object getIdentifier() {
92         return this;
93     }
94
95     @Override
96     public synchronized void put(final LogicalDatastoreType store, final YangInstanceIdentifier path,
97                                  final NormalizedNode<?, ?> data) {
98         checkEditable(store);
99
100         // Trying to write only mixin nodes (not visible when serialized).
101         // Ignoring. Some devices cannot handle empty edit-config rpc
102         if (containsOnlyNonVisibleData(path, data)) {
103             LOG.debug("Ignoring put for {} and data {}. Resulting data structure is empty.", path, data);
104             return;
105         }
106
107         final DataContainerChild<?, ?> editStructure = netOps.createEditConfigStrcture(Optional.ofNullable(data),
108                         Optional.of(ModifyAction.REPLACE), path);
109         editConfig(path, Optional.ofNullable(data), editStructure, Optional.empty(), "put");
110     }
111
112     @Override
113     public synchronized void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path,
114                                    final NormalizedNode<?, ?> data) {
115         checkEditable(store);
116
117         // Trying to write only mixin nodes (not visible when serialized).
118         // Ignoring. Some devices cannot handle empty edit-config rpc
119         if (containsOnlyNonVisibleData(path, data)) {
120             LOG.debug("Ignoring merge for {} and data {}. Resulting data structure is empty.", path, data);
121             return;
122         }
123
124         final DataContainerChild<?, ?> editStructure =  netOps.createEditConfigStrcture(Optional.ofNullable(data),
125             Optional.empty(), path);
126         editConfig(path, Optional.ofNullable(data), editStructure, Optional.empty(), "merge");
127     }
128
129     /**
130      * Check whether the data to be written consists only from mixins.
131      */
132     private static boolean containsOnlyNonVisibleData(final YangInstanceIdentifier path,
133                                                       final NormalizedNode<?, ?> data) {
134         // There's only one such case:top level list (pathArguments == 1 && data is Mixin)
135         // any other mixin nodes are contained by a "regular" node thus visible when serialized
136         return path.getPathArguments().size() == 1 && data instanceof MixinNode;
137     }
138
139     @Override
140     public synchronized void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
141         checkEditable(store);
142         final DataContainerChild<?, ?> editStructure = netOps.createEditConfigStrcture(Optional.empty(),
143                         Optional.of(ModifyAction.DELETE), path);
144         editConfig(path, Optional.empty(), editStructure, Optional.of(ModifyAction.NONE), "delete");
145     }
146
147     @Override
148     public @NonNull FluentFuture<? extends @NonNull CommitInfo> commit() {
149         final SettableFuture<CommitInfo> resultFuture = SettableFuture.create();
150         Futures.addCallback(commitConfiguration(), new FutureCallback<RpcResult<Void>>() {
151             @Override
152             public void onSuccess(final RpcResult<Void> result) {
153                 if (!result.isSuccessful()) {
154                     final Collection<RpcError> errors = result.getErrors();
155                     resultFuture.setException(new TransactionCommitFailedException(
156                         String.format("Commit of transaction %s failed", getIdentifier()),
157                             errors.toArray(new RpcError[errors.size()])));
158                     return;
159                 }
160
161                 resultFuture.set(CommitInfo.empty());
162             }
163
164             @Override
165             public void onFailure(final Throwable failure) {
166                 resultFuture.setException(new TransactionCommitFailedException(
167                         String.format("Commit of transaction %s failed", getIdentifier()), failure));
168             }
169         }, MoreExecutors.directExecutor());
170
171         return FluentFuture.from(resultFuture);
172     }
173
174     protected final ListenableFuture<RpcResult<Void>> commitConfiguration() {
175         listeners.forEach(listener -> listener.onTransactionSubmitted(this));
176         checkNotFinished();
177         finished = true;
178         final ListenableFuture<RpcResult<Void>> result = performCommit();
179         Futures.addCallback(result, new FutureCallback<RpcResult<Void>>() {
180             @Override
181             public void onSuccess(@Nonnull final RpcResult<Void> rpcResult) {
182                 if (rpcResult.isSuccessful()) {
183                     listeners.forEach(txListener -> txListener.onTransactionSuccessful(AbstractWriteTx.this));
184                 } else {
185                     final TransactionCommitFailedException cause =
186                             new TransactionCommitFailedException("Transaction failed",
187                                     rpcResult.getErrors().toArray(new RpcError[rpcResult.getErrors().size()]));
188                     listeners.forEach(listener -> listener.onTransactionFailed(AbstractWriteTx.this, cause));
189                 }
190             }
191
192             @Override
193             public void onFailure(final Throwable throwable) {
194                 listeners.forEach(listener -> listener.onTransactionFailed(AbstractWriteTx.this, throwable));
195             }
196         }, MoreExecutors.directExecutor());
197         return result;
198     }
199
200     protected abstract ListenableFuture<RpcResult<Void>> performCommit();
201
202     private void checkEditable(final LogicalDatastoreType store) {
203         checkNotFinished();
204         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION,
205                 "Can edit only configuration data, not %s", store);
206     }
207
208     protected abstract void editConfig(YangInstanceIdentifier path, Optional<NormalizedNode<?, ?>> data,
209                                        DataContainerChild<?, ?> editStructure,
210                                        Optional<ModifyAction> defaultOperation, String operation);
211
212     protected ListenableFuture<RpcResult<Void>> resultsToTxStatus() {
213         final SettableFuture<RpcResult<Void>> transformed = SettableFuture.create();
214
215         Futures.addCallback(Futures.allAsList(resultsFutures), new FutureCallback<List<DOMRpcResult>>() {
216             @Override
217             public void onSuccess(@Nonnull final List<DOMRpcResult> domRpcResults) {
218                 if (!transformed.isDone()) {
219                     extractResult(domRpcResults, transformed);
220                 }
221             }
222
223             @Override
224             public void onFailure(final Throwable throwable) {
225                 final NetconfDocumentedException exception =
226                         new NetconfDocumentedException(
227                                 id + ":RPC during tx returned an exception" + throwable.getMessage(),
228                                 new Exception(throwable),
229                                 DocumentedException.ErrorType.APPLICATION,
230                                 DocumentedException.ErrorTag.OPERATION_FAILED,
231                                 DocumentedException.ErrorSeverity.ERROR);
232                 transformed.setException(exception);
233             }
234         }, MoreExecutors.directExecutor());
235
236         return transformed;
237     }
238
239     private void extractResult(final List<DOMRpcResult> domRpcResults,
240                                final SettableFuture<RpcResult<Void>> transformed) {
241         DocumentedException.ErrorType errType = DocumentedException.ErrorType.APPLICATION;
242         DocumentedException.ErrorSeverity errSeverity = DocumentedException.ErrorSeverity.ERROR;
243         StringBuilder msgBuilder = new StringBuilder();
244         boolean errorsEncouneterd = false;
245         String errorTag = "operation-failed";
246
247         for (final DOMRpcResult domRpcResult : domRpcResults) {
248             if (!domRpcResult.getErrors().isEmpty()) {
249                 errorsEncouneterd = true;
250                 final RpcError error = domRpcResult.getErrors().iterator().next();
251                 final RpcError.ErrorType errorType = error.getErrorType();
252                 switch (errorType) {
253                     case RPC:
254                         errType = DocumentedException.ErrorType.RPC;
255                         break;
256                     case PROTOCOL:
257                         errType = DocumentedException.ErrorType.PROTOCOL;
258                         break;
259                     case TRANSPORT:
260                         errType = DocumentedException.ErrorType.TRANSPORT;
261                         break;
262                     case APPLICATION:
263                         errType = DocumentedException.ErrorType.APPLICATION;
264                         break;
265                     default:
266                         errType = DocumentedException.ErrorType.APPLICATION;
267                         break;
268                 }
269                 final RpcError.ErrorSeverity severity = error.getSeverity();
270                 switch (severity) {
271                     case ERROR:
272                         errSeverity = DocumentedException.ErrorSeverity.ERROR;
273                         break;
274                     case WARNING:
275                         errSeverity = DocumentedException.ErrorSeverity.WARNING;
276                         break;
277                     default:
278                         errSeverity = DocumentedException.ErrorSeverity.ERROR;
279                         break;
280                 }
281                 msgBuilder.append(error.getMessage());
282                 errorTag = error.getTag();
283             }
284         }
285         if (errorsEncouneterd) {
286             final NetconfDocumentedException exception = new NetconfDocumentedException(id
287                     + ":RPC during tx failed. " + msgBuilder.toString(),
288                     errType,
289                     DocumentedException.ErrorTag.from(errorTag),
290                     errSeverity);
291             transformed.setException(exception);
292             return;
293         }
294         transformed.set(RpcResultBuilder.<Void>success().build());
295     }
296
297     AutoCloseable addListener(final TxListener listener) {
298         listeners.add(listener);
299         return () -> listeners.remove(listener);
300     }
301 }