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;
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;
47 public class NetconfDataTreeServiceImpl implements NetconfDataTreeService {
48 private static final Logger LOG = LoggerFactory.getLogger(NetconfDataTreeServiceImpl.class);
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;
56 private boolean isLockAllowed = true;
58 public NetconfDataTreeServiceImpl(final RemoteDeviceId id, final MountPointContext mountContext,
59 final DOMRpcService rpc,
60 final NetconfSessionPreferences netconfSessionPreferences) {
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());
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);
82 lockRunning(resultsFutures);
84 return resultsFutures;
88 public synchronized void unlock() {
89 if (candidateSupported) {
91 if (runningWritable) {
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.
104 public void discardChanges() {
105 if (candidateSupported) {
106 netconfOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id));
111 public ListenableFuture<Optional<NormalizedNode<?, ?>>> get(YangInstanceIdentifier path) {
112 return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
116 public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfig(final YangInstanceIdentifier path) {
117 return netconfOps.getConfigRunningData(
118 new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
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);
130 return editConfig(defaultOperation, editStructure);
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);
143 return editConfig(defaultOperation, editStructure);
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);
155 return editConfig(defaultOperation, editStructure);
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);
164 return editConfig(Optional.empty(), editStructure);
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);
173 return editConfig(Optional.empty(), editStructure);
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<>() {
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()])));
190 resultFuture.set(CommitInfo.empty());
194 public void onFailure(final Throwable failure) {
195 resultFuture.setException(new TransactionCommitFailedException(
196 String.format("Commit of transaction %s failed", this), failure));
198 }, MoreExecutors.directExecutor());
203 public Object getDeviceId() {
207 void setLockAllowed(final boolean isLockAllowedOrig) {
208 this.isLockAllowed = isLockAllowedOrig;
211 private ListenableFuture<? extends DOMRpcResult> editConfig(final Optional<ModifyAction> defaultOperation,
212 final DataContainerChild<?, ?> editStructure) {
213 if (candidateSupported) {
214 return editConfigCandidate(defaultOperation, editStructure);
216 return editConfigRunning(defaultOperation, editStructure);
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);
226 return netconfOps.editConfigRunning(callback, editStructure, rollbackSupport);
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);
236 return netconfOps.editConfigCandidate(callback, editStructure, rollbackSupport);
240 private void lockRunning(List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
242 resultsFutures.add(netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id)));
244 LOG.trace("Lock is not allowed: {}", id);
248 private void unlockRunning() {
250 netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
252 LOG.trace("Unlock is not allowed: {}", id);
256 private void lockCandidate(List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
258 resultsFutures.add(netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id) {
260 public void onFailure(Throwable throwable) {
261 super.onFailure(throwable);
266 LOG.trace("Lock is not allowed: {}", id);
270 private void unlockCandidate() {
272 netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id));
274 LOG.trace("Unlock is not allowed: {}", id);
278 private void checkEditable(final LogicalDatastoreType store) {
279 checkArgument(store == LogicalDatastoreType.CONFIGURATION,
280 "Can edit only configuration data, not %s", store);
283 private synchronized ListenableFuture<RpcResult<Void>> performCommit(
284 final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
285 if (!candidateSupported) {
287 return resultsToStatus(id, resultsFutures);
289 resultsFutures.add(netconfOps.commit(new NetconfRpcFutureCallback("Commit", id)));
290 final ListenableFuture<RpcResult<Void>> result = resultsToStatus(id, resultsFutures);
291 Futures.addCallback(result, new FutureCallback<>() {
293 public void onSuccess(final RpcResult<Void> result) {
298 public void onFailure(final Throwable throwable) {
302 }, MoreExecutors.directExecutor());
306 private static ListenableFuture<RpcResult<Void>> resultsToStatus(
307 final RemoteDeviceId id, List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
308 final SettableFuture<RpcResult<Void>> transformed = SettableFuture.create();
310 Futures.addCallback(Futures.allAsList(resultsFutures), new FutureCallback<>() {
312 public void onSuccess(final List<DOMRpcResult> domRpcResults) {
313 if (!transformed.isDone()) {
314 extractResult(domRpcResults, transformed, id);
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);
329 }, MoreExecutors.directExecutor());
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";
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();
352 errType = DocumentedException.ErrorType.RPC;
355 errType = DocumentedException.ErrorType.PROTOCOL;
358 errType = DocumentedException.ErrorType.TRANSPORT;
362 errType = DocumentedException.ErrorType.APPLICATION;
365 final RpcError.ErrorSeverity severity = error.getSeverity();
368 errSeverity = DocumentedException.ErrorSeverity.WARNING;
372 errSeverity = DocumentedException.ErrorSeverity.ERROR;
375 msgBuilder.add(error.getMessage());
376 msgBuilder.add(error.getInfo());
377 errorTag = error.getTag();
380 if (errorsEncouneterd) {
381 final NetconfDocumentedException exception = new NetconfDocumentedException(id
382 + ":RPC during tx failed. " + msgBuilder.toString(),
384 DocumentedException.ErrorTag.from(errorTag),
386 transformed.setException(exception);
389 transformed.set(RpcResultBuilder.<Void>success().build());