2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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
8 package org.opendaylight.netconf.sal.connect.netconf.sal;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
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;
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);
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.
54 public ListenableFuture<? extends DOMRpcResult> discardChanges() {
55 return netconfOps.discardChanges(new NetconfRpcFutureCallback("Discard candidate", id));
59 ListenableFuture<? extends DOMRpcResult> lockSingle() {
60 return netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id));
64 List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
65 return List.of(netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id)));
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);
77 private static final class Running extends AbstractNetconfDataTreeService {
78 Running(final RemoteDeviceId id, final NetconfBaseOps netconfOps, final boolean rollbackSupport) {
79 super(id, netconfOps, rollbackSupport);
83 public ListenableFuture<DOMRpcResult> discardChanges() {
84 // Changes cannot be discarded from running
89 ListenableFuture<? extends DOMRpcResult> lockSingle() {
90 return netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
94 List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
95 return List.of(netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)));
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);
107 private static final class CandidateWithRunning extends AbstractNetconfDataTreeService {
108 private final Candidate candidate;
109 private final Running running;
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);
119 public ListenableFuture<? extends DOMRpcResult> discardChanges() {
120 return candidate.discardChanges();
124 ListenableFuture<DOMRpcResult> lockSingle() {
125 throw new UnsupportedOperationException();
129 List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
130 return List.of(candidate.lockSingle(), running.lockSingle());
134 List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
135 return List.of(running.unlock(), candidate.unlock());
139 ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild editStructure,
140 final ModifyAction defaultOperation) {
141 return candidate.editConfig(editStructure, defaultOperation);
145 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfDataTreeService.class);
146 private static final ListenableFuture<DOMRpcResult> RPC_SUCCESS =
147 Futures.immediateFuture(new DefaultDOMRpcResult());
149 final @NonNull RemoteDeviceId id;
150 final NetconfBaseOps netconfOps;
151 final boolean rollbackSupport;
153 // FIXME: what do we do with locks acquired before this got flipped?
154 private volatile boolean isLockAllowed = true;
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;
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();
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);
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 "
184 public synchronized ListenableFuture<DOMRpcResult> lock() {
185 if (!isLockAllowed) {
186 LOG.trace("Lock is not allowed by device configuration, ignoring lock results: {}", id);
190 final ListenableFuture<DOMRpcResult> result = mergeFutures(lockImpl());
191 Futures.addCallback(result, new FutureCallback<DOMRpcResult>() {
193 public void onSuccess(final DOMRpcResult result) {
194 final var errors = result.getErrors();
195 if (errors.isEmpty()) {
196 LOG.debug("{}: Lock successful.", id);
199 if (allWarnings(errors)) {
200 LOG.info("{}: Lock successful with warnings {}", errors, id);
204 LOG.warn("{}: Lock failed with errors {}", id, errors);
208 public void onFailure(final Throwable throwable) {
209 LOG.warn("{}: Lock failed.", id, throwable);
211 }, MoreExecutors.directExecutor());
216 List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
217 return List.of(lockSingle());
220 abstract ListenableFuture<? extends DOMRpcResult> lockSingle();
223 public synchronized ListenableFuture<DOMRpcResult> unlock() {
224 // FIXME: deal with lock with lifecycle?
225 if (!isLockAllowed) {
226 LOG.trace("Unlock is not allowed: {}", id);
230 final ListenableFuture<DOMRpcResult> result = mergeFutures(unlockImpl());
231 Futures.addCallback(result, new FutureCallback<DOMRpcResult>() {
233 public void onSuccess(final DOMRpcResult result) {
234 final var errors = result.getErrors();
235 if (errors.isEmpty()) {
236 LOG.debug("{}: Unlock successful.", id);
239 if (allWarnings(errors)) {
240 LOG.info("{}: Unlock successful with warnings {}", errors, id);
244 LOG.error("{}: Unlock failed with errors {}", id, errors);
248 public void onFailure(final Throwable throwable) {
249 LOG.error("{}: Unlock failed.", id, throwable);
251 }, MoreExecutors.directExecutor());
255 abstract List<ListenableFuture<? extends DOMRpcResult>> unlockImpl();
258 public ListenableFuture<Optional<NormalizedNode>> get(final YangInstanceIdentifier path) {
259 return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
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);
269 public ListenableFuture<Optional<NormalizedNode>> getConfig(final YangInstanceIdentifier path) {
270 return netconfOps.getConfigRunningData(new NetconfRpcFutureCallback("Data read", id),
271 Optional.ofNullable(path));
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);
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);
287 netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.MERGE), path),
288 defaultOperation.orElse(null));
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);
297 netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.REPLACE), path),
298 defaultOperation.orElse(null));
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);
307 netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.CREATE), path),
308 defaultOperation.orElse(null));
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),
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),
326 public synchronized ListenableFuture<? extends DOMRpcResult> commit() {
327 return netconfOps.commit(new NetconfRpcFutureCallback("Commit", id));
331 public final Object getDeviceId() {
335 final void setLockAllowed(final boolean isLockAllowedOrig) {
336 this.isLockAllowed = isLockAllowedOrig;
339 abstract ListenableFuture<? extends DOMRpcResult> editConfig(DataContainerChild editStructure,
340 @Nullable ModifyAction defaultOperation);
342 private static void checkEditable(final LogicalDatastoreType store) {
343 checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can only edit configuration data, not %s", store);
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) {
352 return Futures.getDone(futures.get(0));
355 final var builder = ImmutableList.<RpcError>builder();
356 for (ListenableFuture<? extends DOMRpcResult> future : futures) {
357 builder.addAll(Futures.getDone(future).getErrors());
359 return new DefaultDOMRpcResult(null, builder.build());
360 }, MoreExecutors.directExecutor());
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);