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 java.util.Collection;
19 import java.util.List;
20 import java.util.Optional;
21 import org.eclipse.jdt.annotation.NonNull;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
24 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
25 import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult;
26 import org.opendaylight.netconf.api.EffectiveOperation;
27 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
28 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceId;
29 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceServices.Rpcs;
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.yangtools.rfc8528.data.api.MountPointContext;
34 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
35 import org.opendaylight.yangtools.yang.common.RpcError;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 public abstract sealed class AbstractNetconfDataTreeService implements NetconfDataTreeService {
43 private static final class Candidate extends AbstractNetconfDataTreeService {
44 Candidate(final RemoteDeviceId id, final NetconfBaseOps netconfOps, final boolean rollbackSupport,
45 final boolean lockDatastore) {
46 super(id, netconfOps, rollbackSupport, lockDatastore);
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 EffectiveOperation 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 final boolean lockDatastore) {
80 super(id, netconfOps, rollbackSupport, lockDatastore);
84 public ListenableFuture<DOMRpcResult> discardChanges() {
85 // Changes cannot be discarded from running
90 public ListenableFuture<DOMRpcResult> commit() {
91 // No candidate, hence we commit immediately
96 ListenableFuture<? extends DOMRpcResult> lockSingle() {
97 return netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
101 List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
102 return List.of(netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id)));
106 ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild editStructure,
107 final EffectiveOperation defaultOperation) {
108 final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit running", id);
109 return defaultOperation == null ? netconfOps.editConfigRunning(callback, editStructure, rollbackSupport)
110 : netconfOps.editConfigRunning(callback, editStructure, defaultOperation, rollbackSupport);
114 private static final class CandidateWithRunning extends AbstractNetconfDataTreeService {
115 private final Candidate candidate;
116 private final Running running;
118 CandidateWithRunning(final RemoteDeviceId id, final NetconfBaseOps netconfOps,
119 final boolean rollbackSupport, final boolean lockDatastore) {
120 super(id, netconfOps, rollbackSupport, lockDatastore);
121 candidate = new Candidate(id, netconfOps, rollbackSupport, lockDatastore);
122 running = new Running(id, netconfOps, rollbackSupport, lockDatastore);
126 public ListenableFuture<? extends DOMRpcResult> discardChanges() {
127 return candidate.discardChanges();
131 ListenableFuture<DOMRpcResult> lockSingle() {
132 throw new UnsupportedOperationException();
136 List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
137 return List.of(candidate.lockSingle(), running.lockSingle());
141 List<ListenableFuture<? extends DOMRpcResult>> unlockImpl() {
142 return List.of(running.unlock(), candidate.unlock());
146 ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild editStructure,
147 final EffectiveOperation defaultOperation) {
148 return candidate.editConfig(editStructure, defaultOperation);
152 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfDataTreeService.class);
153 private static final ListenableFuture<DOMRpcResult> RPC_SUCCESS =
154 Futures.immediateFuture(new DefaultDOMRpcResult());
156 final @NonNull RemoteDeviceId id;
157 final NetconfBaseOps netconfOps;
158 final boolean rollbackSupport;
160 private final boolean lockDatastore;
162 AbstractNetconfDataTreeService(final RemoteDeviceId id, final NetconfBaseOps netconfOps,
163 final boolean rollbackSupport, final boolean lockDatastore) {
164 this.id = requireNonNull(id);
165 this.netconfOps = requireNonNull(netconfOps);
166 this.rollbackSupport = rollbackSupport;
167 this.lockDatastore = lockDatastore;
170 public static @NonNull AbstractNetconfDataTreeService of(final RemoteDeviceId id,
171 final MountPointContext mountContext, final Rpcs rpcs,
172 final NetconfSessionPreferences sessionPreferences, final boolean lockDatastore) {
173 final var netconfOps = new NetconfBaseOps(rpcs, mountContext);
174 final boolean rollbackSupport = sessionPreferences.isRollbackSupported();
176 // Examine preferences and decide which implementation to use
177 if (sessionPreferences.isCandidateSupported()) {
178 return sessionPreferences.isRunningWritable()
179 ? new CandidateWithRunning(id, netconfOps, rollbackSupport, lockDatastore)
180 : new Candidate(id, netconfOps, rollbackSupport, lockDatastore);
181 } else if (sessionPreferences.isRunningWritable()) {
182 return new Running(id, netconfOps, rollbackSupport, lockDatastore);
184 throw new IllegalArgumentException("Device " + id.name() + " has advertised neither :writable-running nor "
185 + ":candidate capability. Failed to establish session, as at least one of these must be advertised.");
190 public synchronized ListenableFuture<DOMRpcResult> lock() {
191 if (!lockDatastore) {
192 LOG.trace("Lock is not allowed by device configuration, ignoring lock results: {}", id);
196 final ListenableFuture<DOMRpcResult> result = mergeFutures(lockImpl());
197 Futures.addCallback(result, new FutureCallback<>() {
199 public void onSuccess(final DOMRpcResult result) {
200 final var errors = result.errors();
201 if (errors.isEmpty()) {
202 LOG.debug("{}: Lock successful.", id);
205 if (allWarnings(errors)) {
206 LOG.info("{}: Lock successful with warnings {}", errors, id);
210 LOG.warn("{}: Lock failed with errors {}", id, errors);
214 public void onFailure(final Throwable throwable) {
215 LOG.warn("{}: Lock failed.", id, throwable);
217 }, MoreExecutors.directExecutor());
222 List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
223 return List.of(lockSingle());
226 abstract ListenableFuture<? extends DOMRpcResult> lockSingle();
229 public synchronized ListenableFuture<DOMRpcResult> unlock() {
230 if (!lockDatastore) {
231 LOG.trace("Unlock is not allowed: {}", id);
235 final ListenableFuture<DOMRpcResult> result = mergeFutures(unlockImpl());
236 Futures.addCallback(result, new FutureCallback<>() {
238 public void onSuccess(final DOMRpcResult result) {
239 final var errors = result.errors();
240 if (errors.isEmpty()) {
241 LOG.debug("{}: Unlock successful.", id);
244 if (allWarnings(errors)) {
245 LOG.info("{}: Unlock successful with warnings {}", errors, id);
249 LOG.error("{}: Unlock failed with errors {}", id, errors);
253 public void onFailure(final Throwable throwable) {
254 LOG.error("{}: Unlock failed.", id, throwable);
256 }, MoreExecutors.directExecutor());
260 abstract List<ListenableFuture<? extends DOMRpcResult>> unlockImpl();
263 public ListenableFuture<Optional<NormalizedNode>> get(final YangInstanceIdentifier path) {
264 return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
268 public ListenableFuture<Optional<NormalizedNode>> get(final YangInstanceIdentifier path,
269 final List<YangInstanceIdentifier> fields) {
270 return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path), fields);
274 public ListenableFuture<Optional<NormalizedNode>> getConfig(final YangInstanceIdentifier path) {
275 return netconfOps.getConfigRunningData(new NetconfRpcFutureCallback("Data read", id),
276 Optional.ofNullable(path));
280 public ListenableFuture<Optional<NormalizedNode>> getConfig(final YangInstanceIdentifier path,
281 final List<YangInstanceIdentifier> fields) {
282 return netconfOps.getConfigRunningData(new NetconfRpcFutureCallback("Data read", id),
283 Optional.ofNullable(path), fields);
287 public synchronized ListenableFuture<? extends DOMRpcResult> merge(final LogicalDatastoreType store,
288 final YangInstanceIdentifier path, final NormalizedNode data,
289 final Optional<EffectiveOperation> defaultOperation) {
290 checkEditable(store);
292 netconfOps.createEditConfigStructure(Optional.ofNullable(data), Optional.of(EffectiveOperation.MERGE),
294 defaultOperation.orElse(null));
298 public synchronized ListenableFuture<? extends DOMRpcResult> replace(final LogicalDatastoreType store,
299 final YangInstanceIdentifier path, final NormalizedNode data,
300 final Optional<EffectiveOperation> defaultOperation) {
301 checkEditable(store);
303 netconfOps.createEditConfigStructure(Optional.ofNullable(data), Optional.of(EffectiveOperation.REPLACE),
305 defaultOperation.orElse(null));
309 public synchronized ListenableFuture<? extends DOMRpcResult> create(final LogicalDatastoreType store,
310 final YangInstanceIdentifier path, final NormalizedNode data,
311 final Optional<EffectiveOperation> defaultOperation) {
312 checkEditable(store);
314 netconfOps.createEditConfigStructure(Optional.ofNullable(data), Optional.of(EffectiveOperation.CREATE),
316 defaultOperation.orElse(null));
320 public synchronized ListenableFuture<? extends DOMRpcResult> delete(final LogicalDatastoreType store,
321 final YangInstanceIdentifier path) {
322 return editConfig(netconfOps.createEditConfigStructure(Optional.empty(),
323 Optional.of(EffectiveOperation.DELETE), path), null);
327 public synchronized ListenableFuture<? extends DOMRpcResult> remove(final LogicalDatastoreType store,
328 final YangInstanceIdentifier path) {
329 return editConfig(netconfOps.createEditConfigStructure(Optional.empty(),
330 Optional.of(EffectiveOperation.REMOVE), path), null);
334 public synchronized ListenableFuture<? extends DOMRpcResult> commit() {
335 return netconfOps.commit(new NetconfRpcFutureCallback("Commit", id));
339 public final Object getDeviceId() {
343 abstract ListenableFuture<? extends DOMRpcResult> editConfig(DataContainerChild editStructure,
344 @Nullable EffectiveOperation defaultOperation);
346 private static void checkEditable(final LogicalDatastoreType store) {
347 checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can only edit configuration data, not %s", store);
350 // Transform list of futures related to RPC operation into a single Future
351 private static ListenableFuture<DOMRpcResult> mergeFutures(
352 final List<ListenableFuture<? extends DOMRpcResult>> futures) {
353 return Futures.whenAllComplete(futures).call(() -> {
354 if (futures.size() == 1) {
356 return Futures.getDone(futures.get(0));
359 final var builder = ImmutableList.<RpcError>builder();
360 for (ListenableFuture<? extends DOMRpcResult> future : futures) {
361 builder.addAll(Futures.getDone(future).errors());
363 return new DefaultDOMRpcResult(null, builder.build());
364 }, MoreExecutors.directExecutor());
367 private static boolean allWarnings(final Collection<? extends @NonNull RpcError> errors) {
368 return errors.stream().allMatch(error -> error.getSeverity() == ErrorSeverity.WARNING);