Bump MRI upstreams
[netconf.git] / netconf / sal-netconf-connector / src / main / java / org / opendaylight / netconf / sal / connect / netconf / sal / AbstractNetconfDataTreeService.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 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.collect.ImmutableList;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.Futures;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import com.google.common.util.concurrent.MoreExecutors;
18 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19 import java.util.Collection;
20 import java.util.List;
21 import java.util.Optional;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
26 import org.opendaylight.mdsal.dom.api.DOMRpcService;
27 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
28 import org.opendaylight.netconf.api.ModifyAction;
29 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
30 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
31 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
32 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfRpcFutureCallback;
33 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
34 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
35 import org.opendaylight.yangtools.yang.common.RpcError;
36 import org.opendaylight.yangtools.yang.common.RpcError.ErrorSeverity;
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.NormalizedNode;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 public abstract class AbstractNetconfDataTreeService implements NetconfDataTreeService {
44     private static final class Candidate extends AbstractNetconfDataTreeService {
45         Candidate(final RemoteDeviceId id, final NetconfBaseOps netconfOps, final boolean rollbackSupport) {
46             super(id, netconfOps, rollbackSupport);
47         }
48
49         /**
50          * This has to be non blocking since it is called from a callback on commit and it is netty threadpool that is
51          * really sensitive to blocking calls.
52          */
53         @Override
54         public ListenableFuture<? extends DOMRpcResult> discardChanges() {
55             return netconfOps.discardChanges(new NetconfRpcFutureCallback("Discard candidate", id));
56         }
57
58         @Override
59         ListenableFuture<? extends DOMRpcResult> lockSingle() {
60             return netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id));
61         }
62
63         @Override
64         List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
65             return List.of(netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id)));
66         }
67
68         @Override
69         ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild editStructure,
70                 final ModifyAction defaultOperation) {
71             final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit candidate", id);
72             return defaultOperation == null ? netconfOps.editConfigCandidate(callback, editStructure, rollbackSupport)
73                 : netconfOps.editConfigCandidate(callback, editStructure, defaultOperation, rollbackSupport);
74         }
75     }
76
77     private static final class Running extends AbstractNetconfDataTreeService {
78         Running(final RemoteDeviceId id, final NetconfBaseOps netconfOps, final boolean rollbackSupport) {
79             super(id, netconfOps, rollbackSupport);
80         }
81
82         @Override
83         public ListenableFuture<DOMRpcResult> discardChanges() {
84             // Changes cannot be discarded from running
85             return RPC_SUCCESS;
86         }
87
88         @Override
89         ListenableFuture<? extends DOMRpcResult> lockSingle() {
90             return netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
91         }
92
93         @Override
94         List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
95             return List.of(netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)));
96         }
97
98         @Override
99         ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild editStructure,
100                 final ModifyAction defaultOperation) {
101             final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit running", id);
102             return defaultOperation == null ? netconfOps.editConfigRunning(callback, editStructure, rollbackSupport)
103                 : netconfOps.editConfigRunning(callback, editStructure, defaultOperation, rollbackSupport);
104         }
105     }
106
107     private static final class CandidateWithRunning extends AbstractNetconfDataTreeService {
108         private final Candidate candidate;
109         private final Running running;
110
111         CandidateWithRunning(final RemoteDeviceId id, final NetconfBaseOps netconfOps,
112                 final boolean rollbackSupport) {
113             super(id, netconfOps, rollbackSupport);
114             candidate = new Candidate(id, netconfOps, rollbackSupport);
115             running = new Running(id, netconfOps, rollbackSupport);
116         }
117
118         @Override
119         public ListenableFuture<? extends DOMRpcResult> discardChanges() {
120             return candidate.discardChanges();
121         }
122
123         @Override
124         ListenableFuture<DOMRpcResult> lockSingle() {
125             throw new UnsupportedOperationException();
126         }
127
128         @Override
129         List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
130             return List.of(candidate.lockSingle(), running.lockSingle());
131         }
132
133         @Override
134         List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
135             return List.of(running.unlock(), candidate.unlock());
136         }
137
138         @Override
139         ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild editStructure,
140                 final ModifyAction defaultOperation) {
141             return candidate.editConfig(editStructure, defaultOperation);
142         }
143     }
144
145     private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfDataTreeService.class);
146     private static final ListenableFuture<DOMRpcResult> RPC_SUCCESS =
147         Futures.immediateFuture(new DefaultDOMRpcResult());
148
149     final @NonNull RemoteDeviceId id;
150     final NetconfBaseOps netconfOps;
151     final boolean rollbackSupport;
152
153     // FIXME: what do we do with locks acquired before this got flipped?
154     private volatile boolean isLockAllowed = true;
155
156     AbstractNetconfDataTreeService(final RemoteDeviceId id, final NetconfBaseOps netconfOps,
157             final boolean rollbackSupport) {
158         this.id = requireNonNull(id);
159         this.netconfOps = requireNonNull(netconfOps);
160         this.rollbackSupport = rollbackSupport;
161     }
162
163     public static @NonNull AbstractNetconfDataTreeService of(final RemoteDeviceId id,
164             final MountPointContext mountContext, final DOMRpcService rpc,
165             final NetconfSessionPreferences netconfSessionPreferences) {
166         final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc, mountContext);
167         final boolean rollbackSupport = netconfSessionPreferences.isRollbackSupported();
168
169         // Examine preferences and decide which implementation to use
170         if (netconfSessionPreferences.isCandidateSupported()) {
171             return netconfSessionPreferences.isRunningWritable()
172                 ? new CandidateWithRunning(id, netconfOps, rollbackSupport)
173                     : new Candidate(id, netconfOps, rollbackSupport);
174         } else if (netconfSessionPreferences.isRunningWritable()) {
175             return new Running(id, netconfOps, rollbackSupport);
176         } else {
177             throw new IllegalArgumentException("Device " + id.getName() + " has advertised neither :writable-running "
178                 + "nor :candidate capability. Failed to establish session, as at least one of these must be "
179                 + "advertised.");
180         }
181     }
182
183     @Override
184     public synchronized ListenableFuture<DOMRpcResult> lock() {
185         if (!isLockAllowed) {
186             LOG.trace("Lock is not allowed by device configuration, ignoring lock results: {}", id);
187             return RPC_SUCCESS;
188         }
189
190         final ListenableFuture<DOMRpcResult> result = mergeFutures(lockImpl());
191         Futures.addCallback(result, new FutureCallback<DOMRpcResult>() {
192             @Override
193             public void onSuccess(final DOMRpcResult result) {
194                 final var errors = result.getErrors();
195                 if (errors.isEmpty()) {
196                     LOG.debug("{}: Lock successful.", id);
197                     return;
198                 }
199                 if (allWarnings(errors)) {
200                     LOG.info("{}: Lock successful with warnings {}", errors, id);
201                     return;
202                 }
203
204                 LOG.warn("{}: Lock failed with errors {}", id, errors);
205             }
206
207             @Override
208             public void onFailure(final Throwable throwable) {
209                 LOG.warn("{}: Lock failed.", id, throwable);
210             }
211         }, MoreExecutors.directExecutor());
212
213         return result;
214     }
215
216     List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
217         return List.of(lockSingle());
218     }
219
220     abstract ListenableFuture<? extends DOMRpcResult> lockSingle();
221
222     @Override
223     public synchronized ListenableFuture<DOMRpcResult> unlock() {
224         // FIXME: deal with lock with lifecycle?
225         if (!isLockAllowed) {
226             LOG.trace("Unlock is not allowed: {}", id);
227             return RPC_SUCCESS;
228         }
229
230         final ListenableFuture<DOMRpcResult> result = mergeFutures(unlockImpl());
231         Futures.addCallback(result, new FutureCallback<DOMRpcResult>() {
232             @Override
233             public void onSuccess(final DOMRpcResult result) {
234                 final var errors = result.getErrors();
235                 if (errors.isEmpty()) {
236                     LOG.debug("{}: Unlock successful.", id);
237                     return;
238                 }
239                 if (allWarnings(errors)) {
240                     LOG.info("{}: Unlock successful with warnings {}", errors, id);
241                     return;
242                 }
243
244                 LOG.error("{}: Unlock failed with errors {}", id, errors);
245             }
246
247             @Override
248             public void onFailure(final Throwable throwable) {
249                 LOG.error("{}: Unlock failed.", id, throwable);
250             }
251         }, MoreExecutors.directExecutor());
252         return result;
253     }
254
255     abstract List<ListenableFuture<? extends DOMRpcResult>> unlockImpl();
256
257     @Override
258     public ListenableFuture<Optional<NormalizedNode>> get(final YangInstanceIdentifier path) {
259         return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
260     }
261
262     @Override
263     public ListenableFuture<Optional<NormalizedNode>> get(final YangInstanceIdentifier path,
264             final List<YangInstanceIdentifier> fields) {
265         return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path), fields);
266     }
267
268     @Override
269     public ListenableFuture<Optional<NormalizedNode>> getConfig(final YangInstanceIdentifier path) {
270         return netconfOps.getConfigRunningData(new NetconfRpcFutureCallback("Data read", id),
271             Optional.ofNullable(path));
272     }
273
274     @Override
275     public ListenableFuture<Optional<NormalizedNode>> getConfig(final YangInstanceIdentifier path,
276             final List<YangInstanceIdentifier> fields) {
277         return netconfOps.getConfigRunningData(new NetconfRpcFutureCallback("Data read", id),
278             Optional.ofNullable(path), fields);
279     }
280
281     @Override
282     public synchronized ListenableFuture<? extends DOMRpcResult> merge(final LogicalDatastoreType store,
283             final YangInstanceIdentifier path, final NormalizedNode data,
284             final Optional<ModifyAction> defaultOperation) {
285         checkEditable(store);
286         return editConfig(
287             netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.MERGE), path),
288             defaultOperation.orElse(null));
289     }
290
291     @Override
292     public synchronized ListenableFuture<? extends DOMRpcResult> replace(final LogicalDatastoreType store,
293             final YangInstanceIdentifier path, final NormalizedNode data,
294             final Optional<ModifyAction> defaultOperation) {
295         checkEditable(store);
296         return editConfig(
297             netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.REPLACE), path),
298             defaultOperation.orElse(null));
299     }
300
301     @Override
302     public synchronized ListenableFuture<? extends DOMRpcResult> create(final LogicalDatastoreType store,
303             final YangInstanceIdentifier path, final NormalizedNode data,
304             final Optional<ModifyAction> defaultOperation) {
305         checkEditable(store);
306         return editConfig(
307             netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.CREATE), path),
308             defaultOperation.orElse(null));
309     }
310
311     @Override
312     public synchronized ListenableFuture<? extends DOMRpcResult> delete(final LogicalDatastoreType store,
313             final YangInstanceIdentifier path) {
314         return editConfig(netconfOps.createEditConfigStrcture(Optional.empty(), Optional.of(ModifyAction.DELETE), path),
315             null);
316     }
317
318     @Override
319     public synchronized ListenableFuture<? extends DOMRpcResult> remove(final LogicalDatastoreType store,
320             final YangInstanceIdentifier path) {
321         return editConfig(netconfOps.createEditConfigStrcture(Optional.empty(), Optional.of(ModifyAction.REMOVE), path),
322             null);
323     }
324
325     @Override
326     public synchronized ListenableFuture<? extends DOMRpcResult> commit() {
327         return netconfOps.commit(new NetconfRpcFutureCallback("Commit", id));
328     }
329
330     @Override
331     public final Object getDeviceId() {
332         return id;
333     }
334
335     final void setLockAllowed(final boolean isLockAllowedOrig) {
336         this.isLockAllowed = isLockAllowedOrig;
337     }
338
339     abstract ListenableFuture<? extends DOMRpcResult> editConfig(DataContainerChild editStructure,
340         @Nullable ModifyAction defaultOperation);
341
342     private static void checkEditable(final LogicalDatastoreType store) {
343         checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can only edit configuration data, not %s", store);
344     }
345
346     // Transform list of futures related to RPC operation into a single Future
347     private static ListenableFuture<DOMRpcResult> mergeFutures(
348             final List<ListenableFuture<? extends DOMRpcResult>> futures) {
349         return Futures.whenAllComplete(futures).call(() -> {
350             if (futures.size() == 1) {
351                 // Fast path
352                 return Futures.getDone(futures.get(0));
353             }
354
355             final var builder = ImmutableList.<RpcError>builder();
356             for (ListenableFuture<? extends DOMRpcResult> future : futures) {
357                 builder.addAll(Futures.getDone(future).getErrors());
358             }
359             return new DefaultDOMRpcResult(null, builder.build());
360         }, MoreExecutors.directExecutor());
361     }
362
363     @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
364         justification = "https://github.com/spotbugs/spotbugs/issues/811")
365     private static boolean allWarnings(final Collection<? extends @NonNull RpcError> errors) {
366         return errors.stream().allMatch(error -> error.getSeverity() == ErrorSeverity.WARNING);
367     }
368 }