Deprecate messagebus-netconf
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / sal / NetconfDataTreeServiceImpl.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import com.google.common.util.concurrent.MoreExecutors;
17 import com.google.common.util.concurrent.SettableFuture;
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.StringJoiner;
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.DOMRpcResult;
28 import org.opendaylight.mdsal.dom.api.DOMRpcService;
29 import org.opendaylight.netconf.api.DocumentedException;
30 import org.opendaylight.netconf.api.ModifyAction;
31 import org.opendaylight.netconf.api.NetconfDocumentedException;
32 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
33 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
34 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
35 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfRpcFutureCallback;
36 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
37 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
38 import org.opendaylight.yangtools.yang.common.RpcError;
39 import org.opendaylight.yangtools.yang.common.RpcResult;
40 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 public class NetconfDataTreeServiceImpl implements NetconfDataTreeService {
48     private static final Logger LOG = LoggerFactory.getLogger(NetconfDataTreeServiceImpl.class);
49
50     private final RemoteDeviceId id;
51     private final NetconfBaseOps netconfOps;
52     private final boolean rollbackSupport;
53     private final boolean candidateSupported;
54     private final boolean runningWritable;
55
56     private boolean isLockAllowed = true;
57
58     public NetconfDataTreeServiceImpl(final RemoteDeviceId id, final MountPointContext mountContext,
59                                       final DOMRpcService rpc,
60                                       final NetconfSessionPreferences netconfSessionPreferences) {
61         this.id = id;
62         this.netconfOps = new NetconfBaseOps(rpc, mountContext);
63         // get specific attributes from netconf preferences and get rid of it
64         // no need to keep the entire preferences object, its quite big with all the capability QNames
65         candidateSupported = netconfSessionPreferences.isCandidateSupported();
66         runningWritable = netconfSessionPreferences.isRunningWritable();
67         rollbackSupport = netconfSessionPreferences.isRollbackSupported();
68         Preconditions.checkArgument(candidateSupported || runningWritable,
69                 "Device %s has advertised neither :writable-running nor :candidate capability."
70                         + "At least one of these should be advertised. Failed to establish a session.", id.getName());
71     }
72
73     @Override
74     public synchronized List<ListenableFuture<? extends DOMRpcResult>> lock() {
75         final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures = new ArrayList<>();
76         if (candidateSupported) {
77             lockCandidate(resultsFutures);
78             if (runningWritable) {
79                 lockRunning(resultsFutures);
80             }
81         } else {
82             lockRunning(resultsFutures);
83         }
84         return resultsFutures;
85     }
86
87     @Override
88     public synchronized void unlock() {
89         if (candidateSupported) {
90             unlockCandidate();
91             if (runningWritable) {
92                 unlockRunning();
93             }
94         } else {
95             unlockRunning();
96         }
97     }
98
99     /**
100      * This has to be non blocking since it is called from a callback on commit
101      * and its netty threadpool that is really sensitive to blocking calls.
102      */
103     @Override
104     public void discardChanges() {
105         if (candidateSupported) {
106             netconfOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id));
107         }
108     }
109
110     @Override
111     public ListenableFuture<Optional<NormalizedNode<?, ?>>> get(YangInstanceIdentifier path) {
112         return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
113     }
114
115     @Override
116     public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfig(final YangInstanceIdentifier path) {
117         return netconfOps.getConfigRunningData(
118                 new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
119     }
120
121     @Override
122     public synchronized ListenableFuture<? extends DOMRpcResult> merge(final LogicalDatastoreType store,
123                                                                        final YangInstanceIdentifier path,
124                                                                        final NormalizedNode<?, ?> data,
125                                                                        final Optional<ModifyAction> defaultOperation) {
126         checkEditable(store);
127         final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data),
128                 Optional.of(ModifyAction.MERGE), path);
129
130         return editConfig(defaultOperation, editStructure);
131     }
132
133     @Override
134     public synchronized ListenableFuture<? extends DOMRpcResult> replace(
135             final LogicalDatastoreType store,
136             final YangInstanceIdentifier path,
137             final NormalizedNode<?, ?> data,
138             final Optional<ModifyAction> defaultOperation) {
139         checkEditable(store);
140         final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data),
141                 Optional.of(ModifyAction.REPLACE), path);
142
143         return editConfig(defaultOperation, editStructure);
144     }
145
146     @Override
147     public synchronized ListenableFuture<? extends DOMRpcResult> create(final LogicalDatastoreType store,
148                                                                         final YangInstanceIdentifier path,
149                                                                         final NormalizedNode<?, ?> data,
150                                                                         final Optional<ModifyAction> defaultOperation) {
151         checkEditable(store);
152         final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data),
153                 Optional.of(ModifyAction.CREATE), path);
154
155         return editConfig(defaultOperation, editStructure);
156     }
157
158     @Override
159     public synchronized ListenableFuture<? extends DOMRpcResult> delete(final LogicalDatastoreType store,
160                                                                         final YangInstanceIdentifier path) {
161         final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.empty(),
162                 Optional.of(ModifyAction.DELETE), path);
163
164         return editConfig(Optional.empty(), editStructure);
165     }
166
167     @Override
168     public synchronized ListenableFuture<? extends DOMRpcResult> remove(final LogicalDatastoreType store,
169                                                                         final YangInstanceIdentifier path) {
170         final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.empty(),
171                 Optional.of(ModifyAction.REMOVE), path);
172
173         return editConfig(Optional.empty(), editStructure);
174     }
175
176     @Override
177     public ListenableFuture<? extends CommitInfo> commit(
178             List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
179         final SettableFuture<CommitInfo> resultFuture = SettableFuture.create();
180         Futures.addCallback(performCommit(resultsFutures), new FutureCallback<>() {
181             @Override
182             public void onSuccess(final RpcResult<Void> result) {
183                 if (!result.isSuccessful()) {
184                     final Collection<RpcError> errors = result.getErrors();
185                     resultFuture.setException(new TransactionCommitFailedException(
186                             String.format("Commit of transaction %s failed", this),
187                             errors.toArray(new RpcError[errors.size()])));
188                     return;
189                 }
190                 resultFuture.set(CommitInfo.empty());
191             }
192
193             @Override
194             public void onFailure(final Throwable failure) {
195                 resultFuture.setException(new TransactionCommitFailedException(
196                         String.format("Commit of transaction %s failed", this), failure));
197             }
198         }, MoreExecutors.directExecutor());
199         return resultFuture;
200     }
201
202     @Override
203     public Object getDeviceId() {
204         return id;
205     }
206
207     void setLockAllowed(final boolean isLockAllowedOrig) {
208         this.isLockAllowed = isLockAllowedOrig;
209     }
210
211     private ListenableFuture<? extends DOMRpcResult> editConfig(final Optional<ModifyAction> defaultOperation,
212                                                                 final DataContainerChild<?, ?> editStructure) {
213         if (candidateSupported) {
214             return editConfigCandidate(defaultOperation, editStructure);
215         } else {
216             return editConfigRunning(defaultOperation, editStructure);
217         }
218     }
219
220     private ListenableFuture<? extends DOMRpcResult> editConfigRunning(final Optional<ModifyAction> defaultOperation,
221                                                                        final DataContainerChild<?, ?> editStructure) {
222         final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit running", id);
223         if (defaultOperation.isPresent()) {
224             return netconfOps.editConfigRunning(callback, editStructure, defaultOperation.get(), rollbackSupport);
225         } else {
226             return netconfOps.editConfigRunning(callback, editStructure, rollbackSupport);
227         }
228     }
229
230     private ListenableFuture<? extends DOMRpcResult> editConfigCandidate(final Optional<ModifyAction> defaultOperation,
231                                                                          final DataContainerChild<?, ?> editStructure) {
232         final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit candidate", id);
233         if (defaultOperation.isPresent()) {
234             return netconfOps.editConfigCandidate(callback, editStructure, defaultOperation.get(), rollbackSupport);
235         } else {
236             return netconfOps.editConfigCandidate(callback, editStructure, rollbackSupport);
237         }
238     }
239
240     private void lockRunning(List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
241         if (isLockAllowed) {
242             resultsFutures.add(netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id)));
243         } else {
244             LOG.trace("Lock is not allowed: {}", id);
245         }
246     }
247
248     private void unlockRunning() {
249         if (isLockAllowed) {
250             netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
251         } else {
252             LOG.trace("Unlock is not allowed: {}", id);
253         }
254     }
255
256     private void lockCandidate(List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
257         if (isLockAllowed) {
258             resultsFutures.add(netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id) {
259                 @Override
260                 public void onFailure(Throwable throwable) {
261                     super.onFailure(throwable);
262                     discardChanges();
263                 }
264             }));
265         } else {
266             LOG.trace("Lock is not allowed: {}", id);
267         }
268     }
269
270     private void unlockCandidate() {
271         if (isLockAllowed) {
272             netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id));
273         } else {
274             LOG.trace("Unlock is not allowed: {}", id);
275         }
276     }
277
278     private void checkEditable(final LogicalDatastoreType store) {
279         checkArgument(store == LogicalDatastoreType.CONFIGURATION,
280                 "Can edit only configuration data, not %s", store);
281     }
282
283     private synchronized ListenableFuture<RpcResult<Void>> performCommit(
284             final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
285         if (!candidateSupported) {
286             unlock();
287             return resultsToStatus(id, resultsFutures);
288         }
289         resultsFutures.add(netconfOps.commit(new NetconfRpcFutureCallback("Commit", id)));
290         final ListenableFuture<RpcResult<Void>> result = resultsToStatus(id, resultsFutures);
291         Futures.addCallback(result, new FutureCallback<>() {
292             @Override
293             public void onSuccess(final RpcResult<Void> result) {
294                 unlock();
295             }
296
297             @Override
298             public void onFailure(final Throwable throwable) {
299                 discardChanges();
300                 unlock();
301             }
302         }, MoreExecutors.directExecutor());
303         return result;
304     }
305
306     private static ListenableFuture<RpcResult<Void>> resultsToStatus(
307             final RemoteDeviceId id, List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
308         final SettableFuture<RpcResult<Void>> transformed = SettableFuture.create();
309
310         Futures.addCallback(Futures.allAsList(resultsFutures), new FutureCallback<>() {
311             @Override
312             public void onSuccess(final List<DOMRpcResult> domRpcResults) {
313                 if (!transformed.isDone()) {
314                     extractResult(domRpcResults, transformed, id);
315                 }
316             }
317
318             @Override
319             public void onFailure(final Throwable throwable) {
320                 final NetconfDocumentedException exception =
321                         new NetconfDocumentedException(
322                                 id + ":RPC during tx returned an exception" + throwable.getMessage(),
323                                 new Exception(throwable),
324                                 DocumentedException.ErrorType.APPLICATION,
325                                 DocumentedException.ErrorTag.OPERATION_FAILED,
326                                 DocumentedException.ErrorSeverity.ERROR);
327                 transformed.setException(exception);
328             }
329         }, MoreExecutors.directExecutor());
330
331         return transformed;
332     }
333
334     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
335             justification = "https://github.com/spotbugs/spotbugs/issues/811")
336     private static void extractResult(final List<DOMRpcResult> domRpcResults,
337                                       final SettableFuture<RpcResult<Void>> transformed,
338                                       final RemoteDeviceId id) {
339         DocumentedException.ErrorType errType = DocumentedException.ErrorType.APPLICATION;
340         DocumentedException.ErrorSeverity errSeverity = DocumentedException.ErrorSeverity.ERROR;
341         StringJoiner msgBuilder = new StringJoiner(" ");
342         boolean errorsEncouneterd = false;
343         String errorTag = "operation-failed";
344
345         for (final DOMRpcResult domRpcResult : domRpcResults) {
346             if (!domRpcResult.getErrors().isEmpty()) {
347                 errorsEncouneterd = true;
348                 final RpcError error = domRpcResult.getErrors().iterator().next();
349                 final RpcError.ErrorType errorType = error.getErrorType();
350                 switch (errorType) {
351                     case RPC:
352                         errType = DocumentedException.ErrorType.RPC;
353                         break;
354                     case PROTOCOL:
355                         errType = DocumentedException.ErrorType.PROTOCOL;
356                         break;
357                     case TRANSPORT:
358                         errType = DocumentedException.ErrorType.TRANSPORT;
359                         break;
360                     case APPLICATION:
361                     default:
362                         errType = DocumentedException.ErrorType.APPLICATION;
363                         break;
364                 }
365                 final RpcError.ErrorSeverity severity = error.getSeverity();
366                 switch (severity) {
367                     case WARNING:
368                         errSeverity = DocumentedException.ErrorSeverity.WARNING;
369                         break;
370                     case ERROR:
371                     default:
372                         errSeverity = DocumentedException.ErrorSeverity.ERROR;
373                         break;
374                 }
375                 msgBuilder.add(error.getMessage());
376                 msgBuilder.add(error.getInfo());
377                 errorTag = error.getTag();
378             }
379         }
380         if (errorsEncouneterd) {
381             final NetconfDocumentedException exception = new NetconfDocumentedException(id
382                     + ":RPC during tx failed. " + msgBuilder.toString(),
383                     errType,
384                     DocumentedException.ErrorTag.from(errorTag),
385                     errSeverity);
386             transformed.setException(exception);
387             return;
388         }
389         transformed.set(RpcResultBuilder.<Void>success().build());
390     }
391 }