Merge "BUG-868: Migrate to SchemaContextListener"
[controller.git] / opendaylight / md-sal / sal-netconf-connector / src / main / java / org / opendaylight / controller / sal / connect / netconf / sal / tx / NetconfDeviceWriteOnlyTx.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
9 package org.opendaylight.controller.sal.connect.netconf.sal.tx;
10
11 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
12 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME;
13 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CONFIG_QNAME;
14 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_DEFAULT_OPERATION_QNAME;
15 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_EDIT_CONFIG_QNAME;
16 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_ERROR_OPTION_QNAME;
17 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_OPERATION_QNAME;
18 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_RUNNING_QNAME;
19 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_TARGET_QNAME;
20 import static org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil.ROLLBACK_ON_ERROR_OPTION;
21
22 import com.google.common.base.Function;
23 import com.google.common.base.Optional;
24 import com.google.common.base.Preconditions;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.Iterables;
27 import com.google.common.collect.Lists;
28 import com.google.common.util.concurrent.CheckedFuture;
29 import com.google.common.util.concurrent.Futures;
30 import com.google.common.util.concurrent.ListenableFuture;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.concurrent.ExecutionException;
35 import javax.annotation.Nullable;
36 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
37 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
38 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
39 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
40 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
41 import org.opendaylight.controller.sal.connect.netconf.util.NetconfMessageTransformUtil;
42 import org.opendaylight.controller.sal.connect.util.RemoteDeviceId;
43 import org.opendaylight.controller.sal.core.api.RpcImplementation;
44 import org.opendaylight.yangtools.yang.common.QName;
45 import org.opendaylight.yangtools.yang.common.RpcError;
46 import org.opendaylight.yangtools.yang.common.RpcResult;
47 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
48 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
49 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
50 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
51 import org.opendaylight.yangtools.yang.data.api.Node;
52 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
54 import org.opendaylight.yangtools.yang.data.impl.ImmutableCompositeNode;
55 import org.opendaylight.yangtools.yang.data.impl.NodeFactory;
56 import org.opendaylight.yangtools.yang.data.impl.util.CompositeNodeBuilder;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 public class NetconfDeviceWriteOnlyTx implements DOMDataWriteTransaction {
61
62     private static final Logger LOG  = LoggerFactory.getLogger(NetconfDeviceWriteOnlyTx.class);
63
64     private final RemoteDeviceId id;
65     private final RpcImplementation rpc;
66     private final DataNormalizer normalizer;
67     private final boolean rollbackSupported;
68     private final CompositeNode targetNode;
69
70     public NetconfDeviceWriteOnlyTx(final RemoteDeviceId id, final RpcImplementation rpc, final DataNormalizer normalizer, final boolean candidateSupported, final boolean rollbackOnErrorSupported) {
71         this.id = id;
72         this.rpc = rpc;
73         this.normalizer = normalizer;
74         this.targetNode = getTargetNode(candidateSupported);
75         this.rollbackSupported = rollbackOnErrorSupported;
76     }
77
78     // FIXME add logging
79
80     @Override
81     public boolean cancel() {
82         if(isCommitted()) {
83             return false;
84         }
85
86         return discardChanges();
87     }
88
89     private boolean isCommitted() {
90         // TODO 732
91         return true;
92     }
93
94     private boolean discardChanges() {
95         // TODO 732
96         return true;
97     }
98
99     // TODO should the edit operations be blocking ?
100
101     @Override
102     public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
103         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store);
104
105         try {
106             final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path);
107             final CompositeNode legacyData = normalizer.toLegacy(path, data);
108             sendEditRpc(createEditConfigStructure(legacyPath, Optional.of(ModifyAction.REPLACE), Optional.fromNullable(legacyData)), Optional.of(ModifyAction.NONE));
109         } catch (final ExecutionException e) {
110             LOG.warn("Error putting data to {}, data: {}, discarding changes", path, data, e);
111             discardChanges();
112             throw new RuntimeException("Error while replacing " + path, e);
113         }
114     }
115
116     @Override
117     public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode<?, ?> data) {
118         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store);
119
120         try {
121             final YangInstanceIdentifier legacyPath = NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path);
122             final CompositeNode legacyData = normalizer.toLegacy(path, data);
123             sendEditRpc(
124                     createEditConfigStructure(legacyPath, Optional.<ModifyAction> absent(), Optional.fromNullable(legacyData)), Optional.<ModifyAction> absent());
125         } catch (final ExecutionException e) {
126             LOG.warn("Error merging data to {}, data: {}, discarding changes", path, data, e);
127             discardChanges();
128             throw new RuntimeException("Error while merging " + path, e);
129         }
130     }
131
132     @Override
133     public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
134         Preconditions.checkArgument(store == LogicalDatastoreType.CONFIGURATION, "Can merge only configuration, not %s", store);
135
136         try {
137             sendEditRpc(createEditConfigStructure(NetconfDeviceReadOnlyTx.toLegacyPath(normalizer, path), Optional.of(ModifyAction.DELETE), Optional.<CompositeNode>absent()), Optional.of(ModifyAction.NONE));
138         } catch (final ExecutionException e) {
139             LOG.warn("Error deleting data {}, discarding changes", path, e);
140             discardChanges();
141             throw new RuntimeException("Error while deleting " + path, e);
142         }
143     }
144
145     @Override
146     public CheckedFuture<Void, TransactionCommitFailedException> submit() {
147         final ListenableFuture<Void> commmitFutureAsVoid = Futures.transform(commit(), new Function<RpcResult<TransactionStatus>, Void>() {
148             @Nullable
149             @Override
150             public Void apply(@Nullable final RpcResult<TransactionStatus> input) {
151                 return null;
152             }
153         });
154
155         return Futures.makeChecked(commmitFutureAsVoid, new Function<Exception, TransactionCommitFailedException>() {
156             @Override
157             public TransactionCommitFailedException apply(final Exception input) {
158                 return new TransactionCommitFailedException("Submit of transaction " + getIdentifier() + " failed", input);
159             }
160         });
161     }
162
163     @Override
164     public ListenableFuture<RpcResult<TransactionStatus>> commit() {
165         // FIXME do not allow commit if closed or failed
166
167         final ListenableFuture<RpcResult<CompositeNode>> rpcResult = rpc.invokeRpc(NetconfMessageTransformUtil.NETCONF_COMMIT_QNAME, getCommitRequest());
168         return Futures.transform(rpcResult, new Function<RpcResult<CompositeNode>, RpcResult<TransactionStatus>>() {
169             @Override
170             public RpcResult<TransactionStatus> apply(@Nullable final RpcResult<CompositeNode> input) {
171                 if(input.isSuccessful()) {
172                     return RpcResultBuilder.success(TransactionStatus.COMMITED).build();
173                 } else {
174                     final RpcResultBuilder<TransactionStatus> failed = RpcResultBuilder.failed();
175                     for (final RpcError rpcError : input.getErrors()) {
176                         failed.withError(rpcError.getErrorType(), rpcError.getTag(), rpcError.getMessage(), rpcError.getApplicationTag(), rpcError.getInfo(), rpcError.getCause());
177                     }
178                     return failed.build();
179                 }
180             }
181         });
182
183         // FIXME 732 detect commit failure
184     }
185
186     private void sendEditRpc(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) throws ExecutionException {
187         final CompositeNode editConfigRequest = createEditConfigRequest(editStructure, defaultOperation);
188         final RpcResult<CompositeNode> rpcResult;
189         try {
190             rpcResult = rpc.invokeRpc(NETCONF_EDIT_CONFIG_QNAME, editConfigRequest).get();
191         } catch (final InterruptedException e) {
192             Thread.currentThread().interrupt();
193             throw new RuntimeException(id + ": Interrupted while waiting for response", e);
194         }
195
196         // Check result
197         if(rpcResult.isSuccessful() == false) {
198             throw new ExecutionException(
199                     String.format("%s: Pre-commit rpc failed, request: %s, errors: %s", id, editConfigRequest, rpcResult.getErrors()), null);
200         }
201     }
202
203     private CompositeNode createEditConfigStructure(final YangInstanceIdentifier dataPath, final Optional<ModifyAction> operation,
204                                                     final Optional<CompositeNode> lastChildOverride) {
205         Preconditions.checkArgument(Iterables.isEmpty(dataPath.getPathArguments()) == false, "Instance identifier with empty path %s", dataPath);
206
207         List<YangInstanceIdentifier.PathArgument> reversedPath = Lists.reverse(dataPath.getPath());
208
209         // Create deepest edit element with expected edit operation
210         CompositeNode previous = getDeepestEditElement(reversedPath.get(0), operation, lastChildOverride);
211
212         // Remove already processed deepest child
213         reversedPath = Lists.newArrayList(reversedPath);
214         reversedPath.remove(0);
215
216         // Create edit structure in reversed order
217         for (final YangInstanceIdentifier.PathArgument arg : reversedPath) {
218             final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
219             builder.setQName(arg.getNodeType());
220
221             addPredicatesToCompositeNodeBuilder(getPredicates(arg), builder);
222
223             builder.add(previous);
224             previous = builder.toInstance();
225         }
226         return ImmutableCompositeNode.create(NETCONF_CONFIG_QNAME, ImmutableList.<Node<?>>of(previous));
227     }
228
229     private void addPredicatesToCompositeNodeBuilder(final Map<QName, Object> predicates, final CompositeNodeBuilder<ImmutableCompositeNode> builder) {
230         for (final Map.Entry<QName, Object> entry : predicates.entrySet()) {
231             builder.addLeaf(entry.getKey(), entry.getValue());
232         }
233     }
234
235     private Map<QName, Object> getPredicates(final YangInstanceIdentifier.PathArgument arg) {
236         Map<QName, Object> predicates = Collections.emptyMap();
237         if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
238             predicates = ((YangInstanceIdentifier.NodeIdentifierWithPredicates) arg).getKeyValues();
239         }
240         return predicates;
241     }
242
243     private CompositeNode getDeepestEditElement(final YangInstanceIdentifier.PathArgument arg, final Optional<ModifyAction> operation, final Optional<CompositeNode> lastChildOverride) {
244         final CompositeNodeBuilder<ImmutableCompositeNode> builder = ImmutableCompositeNode.builder();
245         builder.setQName(arg.getNodeType());
246
247         final Map<QName, Object> predicates = getPredicates(arg);
248         addPredicatesToCompositeNodeBuilder(predicates, builder);
249
250         if (operation.isPresent()) {
251             builder.setAttribute(NETCONF_OPERATION_QNAME, modifyOperationToXmlString(operation.get()));
252         }
253         if (lastChildOverride.isPresent()) {
254             final List<Node<?>> children = lastChildOverride.get().getValue();
255             for(final Node<?> child : children) {
256                 if(!predicates.containsKey(child.getKey())) {
257                     builder.add(child);
258                 }
259             }
260         }
261
262         return builder.toInstance();
263     }
264
265     private CompositeNode createEditConfigRequest(final CompositeNode editStructure, final Optional<ModifyAction> defaultOperation) {
266         final CompositeNodeBuilder<ImmutableCompositeNode> ret = ImmutableCompositeNode.builder();
267
268         // Target
269         final Node<?> targetWrapperNode = ImmutableCompositeNode.create(NETCONF_TARGET_QNAME, ImmutableList.<Node<?>>of(targetNode));
270         ret.add(targetWrapperNode);
271
272         // Default operation
273         if(defaultOperation.isPresent()) {
274             final SimpleNode<String> defOp = NodeFactory.createImmutableSimpleNode(NETCONF_DEFAULT_OPERATION_QNAME, null, modifyOperationToXmlString(defaultOperation.get()));
275             ret.add(defOp);
276         }
277
278         // Error option
279         if(rollbackSupported) {
280             ret.addLeaf(NETCONF_ERROR_OPTION_QNAME, ROLLBACK_ON_ERROR_OPTION);
281         }
282
283         ret.setQName(NETCONF_EDIT_CONFIG_QNAME);
284         // Edit content
285         ret.add(editStructure);
286         return ret.toInstance();
287     }
288
289     private String modifyOperationToXmlString(final ModifyAction operation) {
290         return operation.name().toLowerCase();
291     }
292
293     public CompositeNode getTargetNode(final boolean candidateSupported) {
294         if(candidateSupported) {
295             return ImmutableCompositeNode.create(NETCONF_CANDIDATE_QNAME, ImmutableList.<Node<?>>of());
296         } else {
297             return ImmutableCompositeNode.create(NETCONF_RUNNING_QNAME, ImmutableList.<Node<?>>of());
298         }
299     }
300
301     private ImmutableCompositeNode getCommitRequest() {
302         final CompositeNodeBuilder<ImmutableCompositeNode> commitInput = ImmutableCompositeNode.builder();
303         commitInput.setQName(NETCONF_COMMIT_QNAME);
304         return commitInput.toInstance();
305     }
306
307
308     @Override
309     public Object getIdentifier() {
310         return this;
311     }
312 }