Bug 4940 - correctly implement default-request-timeout-millis
[controller.git] / opendaylight / md-sal / sal-netconf-connector / src / main / java / org / opendaylight / controller / sal / connect / netconf / sal / tx / ReadOnlyTx.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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.
7  */
8 package org.opendaylight.controller.sal.connect.netconf.sal.tx;
9
10 import com.google.common.base.Function;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.util.concurrent.CheckedFuture;
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
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
21
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
24 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
25 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
26 import org.opendaylight.controller.sal.connect.netconf.util.NetconfBaseOps;
27 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
28 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
29 import org.opendaylight.yangtools.util.concurrent.MappingCheckedFuture;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
33 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38
39 public final class ReadOnlyTx implements DOMDataReadOnlyTransaction {
40
41     private static final Logger LOG  = LoggerFactory.getLogger(ReadOnlyTx.class);
42
43     private final NetconfBaseOps netconfOps;
44     private final RemoteDeviceId id;
45     private final FutureCallback<DOMRpcResult> loggingCallback;
46
47     private final long requestTimeoutMillis;
48
49     public ReadOnlyTx(final NetconfBaseOps netconfOps, final RemoteDeviceId id, final long requestTimeoutMillis) {
50         this.netconfOps = netconfOps;
51         this.id = id;
52         this.requestTimeoutMillis = requestTimeoutMillis;
53
54         // Simple logging callback to log result of read operation
55         loggingCallback = new FutureCallback<DOMRpcResult>() {
56             @Override
57             public void onSuccess(final DOMRpcResult result) {
58                 if(AbstractWriteTx.isSuccess(result)) {
59                     LOG.trace("{}: Reading data successful", id);
60                 } else {
61                     LOG.warn("{}: Reading data unsuccessful: {}", id, result.getErrors());
62                 }
63             }
64
65             @Override
66             public void onFailure(final Throwable t) {
67                 LOG.warn("{}: Reading data failed", id, t);
68             }
69         };
70     }
71
72     private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readConfigurationData(
73             final YangInstanceIdentifier path) {
74         final ListenableFuture<DOMRpcResult> configRunning = netconfOps.getConfigRunning(loggingCallback, Optional.fromNullable(path));
75
76         final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configRunning, new Function<DOMRpcResult, Optional<NormalizedNode<?, ?>>>() {
77             @Override
78             public Optional<NormalizedNode<?, ?>> apply(final DOMRpcResult result) {
79                 checkReadSuccess(result, path);
80
81                 final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> dataNode = findDataNode(result);
82                 return NormalizedNodes.findNode(dataNode, path.getPathArguments());
83             }
84         });
85
86
87         if(!readWithTimeout("readConfigurationData", configRunning)) {
88             return null;
89         }
90
91         return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
92     }
93
94     private DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> findDataNode(final DOMRpcResult result) {
95         return ((ContainerNode) result.getResult()).getChild(NetconfMessageTransformUtil.toId(NetconfMessageTransformUtil.NETCONF_DATA_QNAME)).get();
96     }
97
98     private void checkReadSuccess(final DOMRpcResult result, final YangInstanceIdentifier path) {
99         try {
100             Preconditions.checkArgument(AbstractWriteTx.isSuccess(result), "%s: Unable to read data: %s, errors: %s", id, path, result.getErrors());
101         } catch (final IllegalArgumentException e) {
102             LOG.warn("{}: Unable to read data: {}, errors: {}", id, path, result.getErrors());
103             throw e;
104         }
105     }
106
107     private CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> readOperationalData(
108             final YangInstanceIdentifier path) {
109         final ListenableFuture<DOMRpcResult> configCandidate = netconfOps.get(loggingCallback, Optional.fromNullable(path));
110
111         // Find data node and normalize its content
112         final ListenableFuture<Optional<NormalizedNode<?, ?>>> transformedFuture = Futures.transform(configCandidate, new Function<DOMRpcResult, Optional<NormalizedNode<?, ?>>>() {
113             @Override
114             public Optional<NormalizedNode<?, ?>> apply(final DOMRpcResult result) {
115                 checkReadSuccess(result, path);
116
117                 final DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?> dataNode = findDataNode(result);
118                 return NormalizedNodes.findNode(dataNode, path.getPathArguments());
119             }
120         });
121
122         if(!readWithTimeout("readOperationalData", configCandidate)) {
123             return null;
124         }
125
126         return MappingCheckedFuture.create(transformedFuture, ReadFailedException.MAPPER);
127     }
128
129     @Override
130     public void close() {
131         // NOOP
132     }
133
134     @Override
135     public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
136             final LogicalDatastoreType store, final YangInstanceIdentifier path) {
137         switch (store) {
138             case CONFIGURATION : {
139                 return readConfigurationData(path);
140             }
141             case OPERATIONAL : {
142                 return readOperationalData(path);
143             }
144         }
145
146         throw new IllegalArgumentException(String.format("%s, Cannot read data %s for %s datastore, unknown datastore type", id, path, store));
147     }
148
149     @Override
150     public CheckedFuture<Boolean, ReadFailedException> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
151         final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> data = read(store, path);
152
153         try {
154             return Futures.immediateCheckedFuture(data.get().isPresent());
155         } catch (InterruptedException | ExecutionException e) {
156             return Futures.immediateFailedCheckedFuture(new ReadFailedException("Exists failed",e));
157         }
158     }
159
160     @Override
161     public Object getIdentifier() {
162         return this;
163     }
164
165     private boolean readWithTimeout(String operation, ListenableFuture<DOMRpcResult> future) {
166         try {
167             future.get(requestTimeoutMillis, TimeUnit.MILLISECONDS);
168         } catch (InterruptedException | ExecutionException e) {
169             LOG.error("{}: {} failed with error", id, operation, e);
170             throw new RuntimeException(id + ": readOperationalData failed");
171         } catch (TimeoutException e) {
172             LOG.warn("{}: Unable to {} after {} milliseconds", id, operation, requestTimeoutMillis, e);
173             future.cancel(true);
174             return false;
175         }
176         return true;
177     }
178 }