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 org.opendaylight.mdsal.common.api.CommitInfo;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
26 import org.opendaylight.mdsal.dom.api.DOMRpcResult;
27 import org.opendaylight.mdsal.dom.api.DOMRpcService;
28 import org.opendaylight.netconf.api.DocumentedException;
29 import org.opendaylight.netconf.api.ModifyAction;
30 import org.opendaylight.netconf.api.NetconfDocumentedException;
31 import org.opendaylight.netconf.dom.api.NetconfDataTreeService;
32 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
33 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
34 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfRpcFutureCallback;
35 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
36 import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext;
37 import org.opendaylight.yangtools.yang.common.RpcError;
38 import org.opendaylight.yangtools.yang.common.RpcResult;
39 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 public class NetconfDataTreeServiceImpl implements NetconfDataTreeService {
47 private static final Logger LOG = LoggerFactory.getLogger(NetconfDataTreeServiceImpl.class);
49 private final RemoteDeviceId id;
50 private final NetconfBaseOps netconfOps;
51 private final boolean rollbackSupport;
52 private final boolean candidateSupported;
53 private final boolean runningWritable;
55 private boolean isLockAllowed = true;
57 public NetconfDataTreeServiceImpl(final RemoteDeviceId id, final MountPointContext mountContext,
58 final DOMRpcService rpc,
59 final NetconfSessionPreferences netconfSessionPreferences) {
61 this.netconfOps = new NetconfBaseOps(rpc, mountContext);
62 // get specific attributes from netconf preferences and get rid of it
63 // no need to keep the entire preferences object, its quite big with all the capability QNames
64 candidateSupported = netconfSessionPreferences.isCandidateSupported();
65 runningWritable = netconfSessionPreferences.isRunningWritable();
66 rollbackSupport = netconfSessionPreferences.isRollbackSupported();
67 Preconditions.checkArgument(candidateSupported || runningWritable,
68 "Device %s has advertised neither :writable-running nor :candidate capability."
69 + "At least one of these should be advertised. Failed to establish a session.", id.getName());
73 public synchronized List<ListenableFuture<? extends DOMRpcResult>> lock() {
74 final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures = new ArrayList<>();
75 if (candidateSupported) {
76 lockCandidate(resultsFutures);
77 if (runningWritable) {
78 lockRunning(resultsFutures);
81 lockRunning(resultsFutures);
83 return resultsFutures;
87 public synchronized void unlock() {
88 if (candidateSupported) {
90 if (runningWritable) {
99 * This has to be non blocking since it is called from a callback on commit
100 * and its netty threadpool that is really sensitive to blocking calls.
103 public void discardChanges() {
104 if (candidateSupported) {
105 netconfOps.discardChanges(new NetconfRpcFutureCallback("Discarding candidate", id));
110 public ListenableFuture<Optional<NormalizedNode<?, ?>>> get(YangInstanceIdentifier path) {
111 return netconfOps.getData(new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
115 public ListenableFuture<Optional<NormalizedNode<?, ?>>> getConfig(final YangInstanceIdentifier path) {
116 return netconfOps.getConfigRunningData(
117 new NetconfRpcFutureCallback("Data read", id), Optional.ofNullable(path));
121 public synchronized ListenableFuture<? extends DOMRpcResult> merge(final LogicalDatastoreType store,
122 final YangInstanceIdentifier path,
123 final NormalizedNode<?, ?> data,
124 final Optional<ModifyAction> defaultOperation) {
125 checkEditable(store);
126 final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data),
127 Optional.of(ModifyAction.MERGE), path);
129 return editConfig(defaultOperation, editStructure);
133 public synchronized ListenableFuture<? extends DOMRpcResult> replace(
134 final LogicalDatastoreType store,
135 final YangInstanceIdentifier path,
136 final NormalizedNode<?, ?> data,
137 final Optional<ModifyAction> defaultOperation) {
138 checkEditable(store);
139 final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data),
140 Optional.of(ModifyAction.REPLACE), path);
142 return editConfig(defaultOperation, editStructure);
146 public synchronized ListenableFuture<? extends DOMRpcResult> create(final LogicalDatastoreType store,
147 final YangInstanceIdentifier path,
148 final NormalizedNode<?, ?> data,
149 final Optional<ModifyAction> defaultOperation) {
150 checkEditable(store);
151 final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.ofNullable(data),
152 Optional.of(ModifyAction.CREATE), path);
154 return editConfig(defaultOperation, editStructure);
158 public synchronized ListenableFuture<? extends DOMRpcResult> delete(final LogicalDatastoreType store,
159 final YangInstanceIdentifier path) {
160 final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.empty(),
161 Optional.of(ModifyAction.DELETE), path);
163 return editConfig(Optional.empty(), editStructure);
167 public synchronized ListenableFuture<? extends DOMRpcResult> remove(final LogicalDatastoreType store,
168 final YangInstanceIdentifier path) {
169 final DataContainerChild<?, ?> editStructure = netconfOps.createEditConfigStrcture(Optional.empty(),
170 Optional.of(ModifyAction.REMOVE), path);
172 return editConfig(Optional.empty(), editStructure);
176 public ListenableFuture<? extends CommitInfo> commit(
177 List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
178 final SettableFuture<CommitInfo> resultFuture = SettableFuture.create();
179 Futures.addCallback(performCommit(resultsFutures), new FutureCallback<>() {
181 public void onSuccess(final RpcResult<Void> result) {
182 if (!result.isSuccessful()) {
183 final Collection<RpcError> errors = result.getErrors();
184 resultFuture.setException(new TransactionCommitFailedException(
185 String.format("Commit of transaction %s failed", this),
186 errors.toArray(new RpcError[errors.size()])));
189 resultFuture.set(CommitInfo.empty());
193 public void onFailure(final Throwable failure) {
194 resultFuture.setException(new TransactionCommitFailedException(
195 String.format("Commit of transaction %s failed", this), failure));
197 }, MoreExecutors.directExecutor());
202 public Object getDeviceId() {
206 void setLockAllowed(final boolean isLockAllowedOrig) {
207 this.isLockAllowed = isLockAllowedOrig;
210 private ListenableFuture<? extends DOMRpcResult> editConfig(final Optional<ModifyAction> defaultOperation,
211 final DataContainerChild<?, ?> editStructure) {
212 if (candidateSupported) {
213 return editConfigCandidate(defaultOperation, editStructure);
215 return editConfigRunning(defaultOperation, editStructure);
219 private ListenableFuture<? extends DOMRpcResult> editConfigRunning(final Optional<ModifyAction> defaultOperation,
220 final DataContainerChild<?, ?> editStructure) {
221 final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit running", id);
222 if (defaultOperation.isPresent()) {
223 return netconfOps.editConfigRunning(callback, editStructure, defaultOperation.get(), rollbackSupport);
225 return netconfOps.editConfigRunning(callback, editStructure, rollbackSupport);
229 private ListenableFuture<? extends DOMRpcResult> editConfigCandidate(final Optional<ModifyAction> defaultOperation,
230 final DataContainerChild<?, ?> editStructure) {
231 final NetconfRpcFutureCallback callback = new NetconfRpcFutureCallback("Edit candidate", id);
232 if (defaultOperation.isPresent()) {
233 return netconfOps.editConfigCandidate(callback, editStructure, defaultOperation.get(), rollbackSupport);
235 return netconfOps.editConfigCandidate(callback, editStructure, rollbackSupport);
239 private void lockRunning(List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
241 resultsFutures.add(netconfOps.lockRunning(new NetconfRpcFutureCallback("Lock running", id)));
243 LOG.trace("Lock is not allowed: {}", id);
247 private void unlockRunning() {
249 netconfOps.unlockRunning(new NetconfRpcFutureCallback("Unlock running", id));
251 LOG.trace("Unlock is not allowed: {}", id);
255 private void lockCandidate(List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
257 resultsFutures.add(netconfOps.lockCandidate(new NetconfRpcFutureCallback("Lock candidate", id) {
259 public void onFailure(Throwable throwable) {
260 super.onFailure(throwable);
265 LOG.trace("Lock is not allowed: {}", id);
269 private void unlockCandidate() {
271 netconfOps.unlockCandidate(new NetconfRpcFutureCallback("Unlock candidate", id));
273 LOG.trace("Unlock is not allowed: {}", id);
277 private void checkEditable(final LogicalDatastoreType store) {
278 checkArgument(store == LogicalDatastoreType.CONFIGURATION,
279 "Can edit only configuration data, not %s", store);
282 private synchronized ListenableFuture<RpcResult<Void>> performCommit(
283 final List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
284 resultsFutures.add(netconfOps.commit(new NetconfRpcFutureCallback("Commit", id)));
286 final ListenableFuture<RpcResult<Void>> txResult = resultsToTxStatus(id, resultsFutures);
287 Futures.addCallback(txResult, new FutureCallback<>() {
289 public void onSuccess(final RpcResult<Void> result) {
294 public void onFailure(final Throwable throwable) {
298 }, MoreExecutors.directExecutor());
303 private static ListenableFuture<RpcResult<Void>> resultsToTxStatus(
304 final RemoteDeviceId id, List<ListenableFuture<? extends DOMRpcResult>> resultsFutures) {
305 final SettableFuture<RpcResult<Void>> transformed = SettableFuture.create();
307 Futures.addCallback(Futures.allAsList(resultsFutures), new FutureCallback<>() {
309 public void onSuccess(final List<DOMRpcResult> domRpcResults) {
310 if (!transformed.isDone()) {
311 extractResult(domRpcResults, transformed, id);
316 public void onFailure(final Throwable throwable) {
317 final NetconfDocumentedException exception =
318 new NetconfDocumentedException(
319 id + ":RPC during tx returned an exception" + throwable.getMessage(),
320 new Exception(throwable),
321 DocumentedException.ErrorType.APPLICATION,
322 DocumentedException.ErrorTag.OPERATION_FAILED,
323 DocumentedException.ErrorSeverity.ERROR);
324 transformed.setException(exception);
326 }, MoreExecutors.directExecutor());
331 @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
332 justification = "https://github.com/spotbugs/spotbugs/issues/811")
333 private static void extractResult(final List<DOMRpcResult> domRpcResults,
334 final SettableFuture<RpcResult<Void>> transformed,
335 final RemoteDeviceId id) {
336 DocumentedException.ErrorType errType = DocumentedException.ErrorType.APPLICATION;
337 DocumentedException.ErrorSeverity errSeverity = DocumentedException.ErrorSeverity.ERROR;
338 StringBuilder msgBuilder = new StringBuilder();
339 boolean errorsEncouneterd = false;
340 String errorTag = "operation-failed";
342 for (final DOMRpcResult domRpcResult : domRpcResults) {
343 if (!domRpcResult.getErrors().isEmpty()) {
344 errorsEncouneterd = true;
345 final RpcError error = domRpcResult.getErrors().iterator().next();
346 final RpcError.ErrorType errorType = error.getErrorType();
349 errType = DocumentedException.ErrorType.RPC;
352 errType = DocumentedException.ErrorType.PROTOCOL;
355 errType = DocumentedException.ErrorType.TRANSPORT;
359 errType = DocumentedException.ErrorType.APPLICATION;
362 final RpcError.ErrorSeverity severity = error.getSeverity();
365 errSeverity = DocumentedException.ErrorSeverity.WARNING;
369 errSeverity = DocumentedException.ErrorSeverity.ERROR;
372 msgBuilder.append(error.getMessage());
373 msgBuilder.append(error.getInfo());
374 errorTag = error.getTag();
377 if (errorsEncouneterd) {
378 final NetconfDocumentedException exception = new NetconfDocumentedException(id
379 + ":RPC during tx failed. " + msgBuilder.toString(),
381 DocumentedException.ErrorTag.from(errorTag),
383 transformed.setException(exception);
386 transformed.set(RpcResultBuilder.<Void>success().build());