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.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.Collection;
20 import java.util.List;
21 import java.util.Optional;
22 import java.util.StringJoiner;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.mdsal.common.api.CommitInfo;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
28 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
29 import org.opendaylight.mdsal.dom.api.DOMRpcService;
30 import org.opendaylight.netconf.api.DocumentedException;
31 import org.opendaylight.netconf.api.ModifyAction;
32 import org.opendaylight.netconf.api.NetconfDocumentedException;
33 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
34 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
35 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
36 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfRpcFutureCallback;
37 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
38 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
39 import org.opendaylight.yangtools.yang.common.RpcError;
40 import org.opendaylight.yangtools.yang.common.RpcResult;
41 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
42 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
43 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
44 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 public abstract class AbstractNetconfDataTreeService implements NetconfDataTreeService {
49 private static final class Candidate extends AbstractNetconfDataTreeService {
50 Candidate(final RemoteDeviceId id, final NetconfBaseOps netconfOps, final boolean rollbackSupport) {
51 super(id, netconfOps, rollbackSupport);
55 * This has to be non blocking since it is called from a callback on commit and it is netty threadpool that is
56 * really sensitive to blocking calls.
59 public void discardChanges() {
60 netconfOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id));
64 ListenableFuture<? extends DOMRpcResult> lockSingle() {
65 return netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id) {
67 public void onFailure(final Throwable throwable) {
68 super.onFailure(throwable);
76 netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id));
80 ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild<?, ?> editStructure,
81 final ModifyAction defaultOperation) {
82 final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit candidate", id);
83 return defaultOperation == null ? netconfOps.editConfigCandidate(callback, editStructure, rollbackSupport)
84 : netconfOps.editConfigCandidate(callback, editStructure, defaultOperation, rollbackSupport);
88 ListenableFuture<RpcResult<Void>> commitImpl(final List<ListenableFuture<? extends DOMRpcResult>> results) {
89 results.add(netconfOps.commit(new NetconfRpcFutureCallback("Commit", id)));
90 final ListenableFuture<RpcResult<Void>> result = resultsToStatus(id, results);
91 Futures.addCallback(result, new FutureCallback<>() {
93 public void onSuccess(final RpcResult<Void> result) {
98 public void onFailure(final Throwable throwable) {
102 }, MoreExecutors.directExecutor());
107 private static final class Running extends AbstractNetconfDataTreeService {
108 Running(final RemoteDeviceId id, final NetconfBaseOps netconfOps, final boolean rollbackSupport) {
109 super(id, netconfOps, rollbackSupport);
113 public void discardChanges() {
114 // Changes cannot be discarded from running
118 ListenableFuture<? extends DOMRpcResult> lockSingle() {
119 return netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id));
124 netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
128 ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild<?, ?> editStructure,
129 final ModifyAction defaultOperation) {
130 final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit running", id);
131 return defaultOperation == null ? netconfOps.editConfigRunning(callback, editStructure, rollbackSupport)
132 : netconfOps.editConfigRunning(callback, editStructure, defaultOperation, rollbackSupport);
136 ListenableFuture<RpcResult<Void>> commitImpl(final List<ListenableFuture<? extends DOMRpcResult>> results) {
138 return resultsToStatus(id, results);
142 private static final class CandidateWithRunning extends AbstractNetconfDataTreeService {
143 private final Candidate candidate;
144 private final Running running;
146 CandidateWithRunning(final RemoteDeviceId id, final NetconfBaseOps netconfOps,
147 final boolean rollbackSupport) {
148 super(id, netconfOps, rollbackSupport);
149 candidate = new Candidate(id, netconfOps, rollbackSupport);
150 running = new Running(id, netconfOps, rollbackSupport);
154 public void discardChanges() {
155 candidate.discardChanges();
159 ListenableFuture<? extends DOMRpcResult> lockSingle() {
160 throw new UnsupportedOperationException();
164 List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
165 return List.of(candidate.lockSingle(), running.lockSingle());
175 ListenableFuture<? extends DOMRpcResult> editConfig(final DataContainerChild<?, ?> editStructure,
176 final ModifyAction defaultOperation) {
177 return candidate.editConfig(editStructure, defaultOperation);
181 ListenableFuture<RpcResult<Void>> commitImpl(final List<ListenableFuture<? extends DOMRpcResult>> results) {
182 return candidate.commitImpl(results);
186 private static final Logger LOG = LoggerFactory.getLogger(AbstractNetconfDataTreeService.class);
188 final @NonNull RemoteDeviceId id;
189 final NetconfBaseOps netconfOps;
190 final boolean rollbackSupport;
192 // FIXME: what do we do with locks acquired before this got flipped?
193 private volatile boolean isLockAllowed = true;
195 AbstractNetconfDataTreeService(final RemoteDeviceId id, final NetconfBaseOps netconfOps,
196 final boolean rollbackSupport) {
197 this.id = requireNonNull(id);
198 this.netconfOps = requireNonNull(netconfOps);
199 this.rollbackSupport = rollbackSupport;
202 public static @NonNull AbstractNetconfDataTreeService of(final RemoteDeviceId id,
203 final MountPointContext mountContext, final DOMRpcService rpc,
204 final NetconfSessionPreferences netconfSessionPreferences) {
205 final NetconfBaseOps netconfOps = new NetconfBaseOps(rpc, mountContext);
206 final boolean rollbackSupport = netconfSessionPreferences.isRollbackSupported();
208 // Examine preferences and decide which implementation to use
209 if (netconfSessionPreferences.isCandidateSupported()) {
210 return netconfSessionPreferences.isRunningWritable()
211 ? new CandidateWithRunning(id, netconfOps, rollbackSupport)
212 : new Candidate(id, netconfOps, rollbackSupport);
213 } else if (netconfSessionPreferences.isRunningWritable()) {
214 return new Running(id, netconfOps, rollbackSupport);
216 throw new IllegalArgumentException("Device " + id.getName() + " has advertised neither :writable-running "
217 + "nor :candidate capability. Failed to establish session, as at least one of these must be "
223 public synchronized List<ListenableFuture<? extends DOMRpcResult>> lock() {
227 LOG.trace("Lock is not allowed: {}", id);
231 List<ListenableFuture<? extends DOMRpcResult>> lockImpl() {
232 return List.of(lockSingle());
235 abstract ListenableFuture<? extends DOMRpcResult> lockSingle();
238 // FIXME: this should be asynchronous as well
239 public synchronized void unlock() {
240 // FIXME: deal with lock with lifecycle?
244 LOG.trace("Unlock is not allowed: {}", id);
248 abstract void unlockImpl();
251 public ListenableFuture<Optional<NormalizedNode<?, ?>>> get(final YangInstanceIdentifier path) {
252 return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
256 public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfig(final YangInstanceIdentifier path) {
257 return netconfOps.getConfigRunningData(
258 new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
262 public synchronized ListenableFuture<? extends DOMRpcResult> merge(final LogicalDatastoreType store,
263 final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
264 final Optional<ModifyAction> defaultOperation) {
265 checkEditable(store);
267 netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.MERGE), path),
268 defaultOperation.orElse(null));
272 public synchronized ListenableFuture<? extends DOMRpcResult> replace(final LogicalDatastoreType store,
273 final YangInstanceIdentifier path, final NormalizedNode<?, ?> data,
274 final Optional<ModifyAction> defaultOperation) {
275 checkEditable(store);
277 netconfOps.createEditConfigStrcture(Optional.ofNullable(data), Optional.of(ModifyAction.REPLACE), path),
278 defaultOperation.orElse(null));
282 public synchronized ListenableFuture<? extends DOMRpcResult> create(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.CREATE), path),
288 defaultOperation.orElse(null));
292 public synchronized ListenableFuture<? extends DOMRpcResult> delete(final LogicalDatastoreType store,
293 final YangInstanceIdentifier path) {
294 return editConfig(netconfOps.createEditConfigStrcture(Optional.empty(), Optional.of(ModifyAction.DELETE), path),
299 public synchronized ListenableFuture<? extends DOMRpcResult> remove(final LogicalDatastoreType store,
300 final YangInstanceIdentifier path) {
301 return editConfig(netconfOps.createEditConfigStrcture(Optional.empty(), Optional.of(ModifyAction.REMOVE), path),
306 public synchronized ListenableFuture<? extends CommitInfo> commit(
307 final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
308 final SettableFuture<CommitInfo> resultFuture = SettableFuture.create();
309 Futures.addCallback(commitImpl(resultsFutures), new FutureCallback<>() {
311 public void onSuccess(final RpcResult<Void> result) {
312 if (!result.isSuccessful()) {
313 final Collection<RpcError> errors = result.getErrors();
314 resultFuture.setException(new TransactionCommitFailedException(
315 String.format("Commit of transaction %s failed", this),
316 errors.toArray(new RpcError[errors.size()])));
319 resultFuture.set(CommitInfo.empty());
323 public void onFailure(final Throwable failure) {
324 resultFuture.setException(new TransactionCommitFailedException(
325 String.format("Commit of transaction %s failed", this), failure));
327 }, MoreExecutors.directExecutor());
331 abstract ListenableFuture<RpcResult<Void>> commitImpl(List<ListenableFuture<? extends DOMRpcResult>> results);
334 public final Object getDeviceId() {
338 final void setLockAllowed(final boolean isLockAllowedOrig) {
339 this.isLockAllowed = isLockAllowedOrig;
342 abstract ListenableFuture<? extends DOMRpcResult> editConfig(DataContainerChild<?, ?> editStructure,
343 @Nullable ModifyAction defaultOperation);
345 private static void checkEditable(final LogicalDatastoreType store) {
346 checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can only edit configuration data, not %s", store);
349 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
350 justification = "https://github.com/spotbugs/spotbugs/issues/811")
351 private static ListenableFuture<RpcResult<Void>> resultsToStatus(
352 final RemoteDeviceId id, final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
353 final SettableFuture<RpcResult<Void>> transformed = SettableFuture.create();
355 Futures.addCallback(Futures.allAsList(resultsFutures), new FutureCallback<>() {
357 public void onSuccess(final List<DOMRpcResult> domRpcResults) {
358 if (!transformed.isDone()) {
359 extractResult(domRpcResults, transformed, id);
364 public void onFailure(final Throwable throwable) {
365 final NetconfDocumentedException exception =
366 new NetconfDocumentedException(
367 id + ":RPC during tx returned an exception" + throwable.getMessage(),
368 new Exception(throwable),
369 DocumentedException.ErrorType.APPLICATION,
370 DocumentedException.ErrorTag.OPERATION_FAILED,
371 DocumentedException.ErrorSeverity.ERROR);
372 transformed.setException(exception);
374 }, MoreExecutors.directExecutor());
379 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
380 justification = "https://github.com/spotbugs/spotbugs/issues/811")
381 private static void extractResult(final List<DOMRpcResult> domRpcResults,
382 final SettableFuture<RpcResult<Void>> transformed,
383 final RemoteDeviceId id) {
384 DocumentedException.ErrorType errType = DocumentedException.ErrorType.APPLICATION;
385 DocumentedException.ErrorSeverity errSeverity = DocumentedException.ErrorSeverity.ERROR;
386 StringJoiner msgBuilder = new StringJoiner(" ");
387 boolean errorsEncouneterd = false;
388 String errorTag = "operation-failed";
390 for (final DOMRpcResult domRpcResult : domRpcResults) {
391 if (!domRpcResult.getErrors().isEmpty()) {
392 errorsEncouneterd = true;
393 final RpcError error = domRpcResult.getErrors().iterator().next();
394 final RpcError.ErrorType errorType = error.getErrorType();
397 errType = DocumentedException.ErrorType.RPC;
400 errType = DocumentedException.ErrorType.PROTOCOL;
403 errType = DocumentedException.ErrorType.TRANSPORT;
407 errType = DocumentedException.ErrorType.APPLICATION;
410 final RpcError.ErrorSeverity severity = error.getSeverity();
413 errSeverity = DocumentedException.ErrorSeverity.WARNING;
417 errSeverity = DocumentedException.ErrorSeverity.ERROR;
420 msgBuilder.add(error.getMessage());
421 msgBuilder.add(error.getInfo());
422 errorTag = error.getTag();
425 if (errorsEncouneterd) {
426 final NetconfDocumentedException exception = new NetconfDocumentedException(id
427 + ":RPC during tx failed. " + msgBuilder.toString(),
429 DocumentedException.ErrorTag.from(errorTag),
431 transformed.setException(exception);
434 transformed.set(RpcResultBuilder.<Void>success().build());