42aaab4006fac476e852330a87078a2540837a5d
[groupbasedpolicy.git] / renderers / vpp / src / main / java / org / opendaylight / groupbasedpolicy / renderer / vpp / util / GbpNetconfTransaction.java
1 /*
2  * Copyright (c) 2016 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.groupbasedpolicy.renderer.vpp.util;
10
11 import java.util.HashSet;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.concurrent.ExecutionException;
15
16 import javax.annotation.Nonnull;
17
18 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
19 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
20 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
21 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
22 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
23 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
24 import org.opendaylight.groupbasedpolicy.renderer.vpp.commands.AbstractInterfaceCommand;
25 import org.opendaylight.groupbasedpolicy.renderer.vpp.commands.RoutingCommand;
26 import org.opendaylight.groupbasedpolicy.renderer.vpp.commands.interfaces.ConfigCommand;
27 import org.opendaylight.vbd.impl.transaction.VbdNetconfTransaction;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
29 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
30 import org.opendaylight.yangtools.yang.binding.DataObject;
31 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.google.common.base.Optional;
36 import com.google.common.base.Preconditions;
37 import com.google.common.util.concurrent.CheckedFuture;
38
39 public class GbpNetconfTransaction {
40
41     public static final byte RETRY_COUNT = 3;
42     private static final Logger LOG = LoggerFactory.getLogger(GbpNetconfTransaction.class);
43
44     /***
45      * Netconf wrapper for write and delete operation on a Netconf Device
46      * @param vppIid        destination node
47      * @param iid           path for Data to be written to
48      * @param data          data to be written
49      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
50      * @param <T>           data type
51      * @return true if transaction is successful, false otherwise
52      */
53     public static <T extends DataObject> boolean netconfSyncedWrite(@Nonnull final InstanceIdentifier<Node> vppIid,
54         @Nonnull final InstanceIdentifier<T> iid, @Nonnull final T data, byte retryCounter) {
55         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
56         boolean result = write(VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey(), iid, data, retryCounter);
57         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
58         return result;
59     }
60
61     public static <T extends DataObject> boolean netconfSyncedWrite(@Nonnull final InstanceIdentifier<Node> vppIid,
62             @Nonnull final Map<InstanceIdentifier<T>,T> data, byte retryCounter) {
63             VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
64             boolean result = write(VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey(), data, retryCounter);
65             VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
66             return result;
67         }
68
69     /***
70      * Netconf wrapper for merge operation on a Netconf Device
71      * @param vppIid        destination node
72      * @param iid           path for Data to be merged to
73      * @param data          data to be merged
74      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
75      * @param <T>           data type
76      * @return true if transaction is successful, false otherwise
77      */
78     public static <T extends DataObject> boolean netconfSyncedMerge(@Nonnull final InstanceIdentifier<Node> vppIid,
79                                                                     @Nonnull final InstanceIdentifier<T> iid, @Nonnull final T data, byte retryCounter) {
80         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
81         boolean result = merge(VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey(), iid, data, retryCounter);
82         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
83         return result;
84     }
85
86     /***
87      * Netconf wrapper method for synced requests for write operation on a Netconf Device
88      * @param vppIid        destination node
89      * @param command       config command that needs to be executed
90      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
91      * @return true if transaction is successful, false otherwise
92      */
93     public static boolean netconfSyncedWrite(@Nonnull final InstanceIdentifier<Node> vppIid, @Nonnull final ConfigCommand command,
94         byte retryCounter) {
95         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
96         boolean result = write(VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey(), command, retryCounter);
97         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
98         return result;
99     }
100
101     /***
102      * Netconf wrapper method for synced requests for write operation on a Netconf Device
103      * @param vppIid        destination node
104      * @param command       routing command that needs to be executed
105      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
106      * @return true if transaction is successful, false otherwise
107      */
108     public static boolean netconfSyncedWrite(@Nonnull final InstanceIdentifier<Node> vppIid, @Nonnull final RoutingCommand command,
109         byte retryCounter) {
110         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
111         boolean result = write(VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey(), command, retryCounter);
112         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
113         return result;
114     }
115
116     /***
117      * Netconf wrapper method for synced requests for delete operation on a Netconf Device
118      * @param vppIid        destination node
119      * @param iid           path for Data to be written to
120      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
121      * @param <T>           data type
122      * @return true if transaction is successful, false otherwise
123      */
124     public static <T extends DataObject> boolean netconfSyncedDelete(@Nonnull final InstanceIdentifier<Node> vppIid,
125         @Nonnull final InstanceIdentifier<T> iid, byte retryCounter) {
126         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
127         boolean result = deleteIfExists(vppIid, iid, retryCounter);
128         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
129         return result;
130     }
131
132     public static <T extends DataObject> boolean netconfSyncedDelete(@Nonnull final InstanceIdentifier<Node> vppIid,
133             @Nonnull Set<InstanceIdentifier<T>> iids , byte retryCounter) {
134             VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
135             boolean result = deleteIfExists(vppIid, iids, retryCounter);
136             VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
137             return result;
138         }
139
140     /***
141      * Netconf wrapper method for synced requests for delete operation on a Netconf Device
142      * @param vppIid        destination node
143      * @param command       config command that needs to be executed
144      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
145      * @return true if transaction is successful, false otherwise
146      */
147     public static boolean netconfSyncedDelete(@Nonnull final InstanceIdentifier<Node> vppIid,
148         @Nonnull final ConfigCommand command, byte retryCounter) {
149         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
150         boolean result = deleteIfExists(vppIid, command.getIid(), retryCounter);
151         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
152         return result;
153     }
154
155     /***
156      * Netconf wrapper method for synced requests for delete operation on a Netconf Device
157      * @param vppIid        destination node
158      * @param command       routing command that needs to be executed
159      * @param retryCounter  retry counter, will repeat the operation for specified amount of times if transaction fails
160      * @return true if transaction is successful, false otherwise
161      */
162     public static boolean netconfSyncedDelete(@Nonnull final InstanceIdentifier<Node> vppIid,
163         @Nonnull final RoutingCommand command, byte retryCounter) {
164         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().lock();
165         boolean result = deleteIfExists(vppIid, command.getIid(), retryCounter);
166         VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getValue().unlock();
167         return result;
168     }
169
170     /**
171      * Use {@link ConfigCommand} to put data into netconf transaction and submit. Transaction is restarted if failed
172      *
173      * @param mountpoint   to access remote device
174      * @param command      config command with data, datastore type and iid
175      * @param retryCounter number of attempts
176      * @return true if transaction is successful, false otherwise
177      */
178     private static boolean write(final DataBroker mountpoint, final ConfigCommand command, byte retryCounter) {
179         LOG.trace("Netconf WRITE transaction started. RetryCounter: {}", retryCounter);
180         Preconditions.checkNotNull(mountpoint);
181         final ReadWriteTransaction rwTx = mountpoint.newReadWriteTransaction();
182         try {
183             command.execute(rwTx);
184             final CheckedFuture<Void, TransactionCommitFailedException> futureTask = rwTx.submit();
185             futureTask.get();
186             LOG.trace("Netconf WRITE transaction done for command {}", command);
187             return true;
188         } catch (Exception e) {
189             // Retry
190             if (retryCounter > 0) {
191                 LOG.warn("Netconf WRITE transaction failed to {}. Restarting transaction ... ", e.getMessage());
192                 return write(mountpoint, command, --retryCounter);
193             } else {
194                 LOG.warn("Netconf WRITE transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
195                 return false;
196             }
197         }
198     }
199
200     /**
201      * Write data to remote device. Transaction is restarted if failed
202      *
203      * @param mountpoint   to access remote device
204      * @param iid          data identifier
205      * @param data         to write
206      * @param retryCounter number of attempts
207      * @param <T>          generic data type. Has to be child of {@link DataObject}
208      * @return true if transaction is successful, false otherwise
209      */
210     private static <T extends DataObject> boolean write(final DataBroker mountpoint, final InstanceIdentifier<T> iid,
211         final T data, byte retryCounter) {
212         LOG.trace("Netconf WRITE transaction started. RetryCounter: {}", retryCounter);
213         Preconditions.checkNotNull(mountpoint);
214         final ReadWriteTransaction rwTx = mountpoint.newReadWriteTransaction();
215         try {
216             rwTx.put(LogicalDatastoreType.CONFIGURATION, iid, data, true);
217             final CheckedFuture<Void, TransactionCommitFailedException> futureTask = rwTx.submit();
218             futureTask.get();
219             LOG.trace("Netconf WRITE transaction done for {}", iid);
220             return true;
221         } catch (Exception e) {
222             // Retry
223             if (retryCounter > 0) {
224                 LOG.warn("Netconf WRITE transaction failed to {}. Restarting transaction ... ", e.getMessage());
225                 return write(mountpoint, iid, data, --retryCounter);
226             } else {
227                 LOG.warn("Netconf WRITE transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
228                 return false;
229             }
230         }
231     }
232
233     /**
234      * Merge data to remote device. Transaction is restarted if failed
235      *
236      * @param mountpoint   to access remote device
237      * @param iid          data identifier
238      * @param data         to merge
239      * @param retryCounter number of attempts
240      * @param <T>          generic data type. Has to be child of {@link DataObject}
241      * @return true if transaction is successful, false otherwise
242      */
243     private static <T extends DataObject> boolean merge(final DataBroker mountpoint, final InstanceIdentifier<T> iid,
244                                                         final T data, byte retryCounter) {
245         LOG.trace("Netconf MERGE transaction started. RetryCounter: {}", retryCounter);
246         Preconditions.checkNotNull(mountpoint);
247         final ReadWriteTransaction rwTx = mountpoint.newReadWriteTransaction();
248         try {
249             rwTx.merge(LogicalDatastoreType.CONFIGURATION, iid, data, true);
250             final CheckedFuture<Void, TransactionCommitFailedException> futureTask = rwTx.submit();
251             futureTask.get();
252             LOG.trace("Netconf MERGE transaction done for {}", iid);
253             return true;
254         } catch (Exception e) {
255             // Retry
256             if (retryCounter > 0) {
257                 LOG.warn("Netconf MERGE transaction failed to {}. Restarting transaction ... ", e.getMessage());
258                 return write(mountpoint, iid, data, --retryCounter);
259             } else {
260                 LOG.warn("Netconf MERGE transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
261                 return false;
262             }
263         }
264     }
265
266     private static <T extends DataObject> boolean write(final DataBroker mountpoint,
267             @Nonnull final Map<InstanceIdentifier<T>, T> data, byte retryCounter) {
268         LOG.trace("Netconf WRITE transaction started. RetryCounter: {}", retryCounter);
269         Preconditions.checkNotNull(mountpoint);
270         Preconditions.checkNotNull(data);
271         Preconditions.checkArgument(!data.isEmpty());
272         final ReadWriteTransaction rwTx = mountpoint.newReadWriteTransaction();
273         try {
274             data.forEach((k, v) -> {
275                 rwTx.put(LogicalDatastoreType.CONFIGURATION, k, v, true);
276             });
277             final CheckedFuture<Void, TransactionCommitFailedException> futureTask = rwTx.submit();
278             futureTask.get();
279             LOG.trace("Netconf WRITE transaction done for {}",
280                     data.keySet().stream().map(iid -> iid.getPathArguments()));
281             return true;
282         } catch (Exception e) {
283             // Retry
284             if (retryCounter > 0) {
285                 LOG.warn("Netconf WRITE transaction failed to {}. Restarting transaction ... ", e.getMessage());
286                 return write(mountpoint, data, --retryCounter);
287             } else {
288                 LOG.warn("Netconf WRITE transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
289                 return false;
290             }
291         }
292     }
293
294     /**
295      * Read data from remote device. Transaction is restarted if failed.
296      *
297      * @param datastoreType {@link LogicalDatastoreType}
298      * @param vppIid        destination node
299      * @param iid           data identifier
300      * @param retryCounter  number of attempts
301      * @param <T>           generic data type. Has to be child of {@link DataObject}
302      * @return optional data object if successful, {@link Optional#absent()} if failed
303      */
304     public static synchronized <T extends DataObject> Optional<T> read(final InstanceIdentifier<Node> vppIid,
305         final LogicalDatastoreType datastoreType, final InstanceIdentifier<T> iid, byte retryCounter) {
306         LOG.trace("Netconf READ transaction started. RetryCounter: {}", retryCounter);
307         Preconditions.checkNotNull(vppIid);
308         DataBroker mountpoint = VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey();
309         final ReadOnlyTransaction rTx = mountpoint.newReadOnlyTransaction();
310         Optional<T> data;
311         try {
312             final CheckedFuture<Optional<T>, ReadFailedException> futureData =
313                 rTx.read(datastoreType, iid);
314             data = futureData.get();
315             LOG.trace("Netconf READ transaction done. Data present: {}", data.isPresent());
316             return data;
317         } catch (Exception e) {
318             // Retry
319             if (retryCounter > 0) {
320                 LOG.warn("Netconf READ transaction failed to {}. Restarting transaction ... ", e.getMessage());
321                 rTx.close();
322                 return read(vppIid, datastoreType, iid, --retryCounter);
323             } else {
324                 LOG.warn("Netconf READ transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
325                 return Optional.absent();
326             }
327         }
328     }
329
330     /**
331      * Remove data from remote device using {@link ConfigCommand}
332      * @param vppIid       destination node
333      * @param command      config command with data, datastore type and iid
334      * @param retryCounter number of attempts
335      * @return true if transaction is successful, false otherwise
336      */
337     private static boolean deleteIfExists(final InstanceIdentifier<Node> vppIid, final AbstractInterfaceCommand command, byte retryCounter) {
338         Preconditions.checkNotNull(vppIid);
339         InstanceIdentifier<Interface> iid = VppIidFactory.getInterfaceIID(command.getInterfaceBuilder().getKey());
340         return deleteIfExists(vppIid, iid, retryCounter);
341     }
342
343     /**
344      * Remove data from remote device. Data presence is verified before removal. Transaction is restarted if failed.
345      * @param vppIid       destination node
346      * @param iid          data identifier
347      * @param retryCounter number of attempts
348      * @param <T>          generic data type. Has to be child of {@link DataObject}
349      * @return true if transaction is successful, false otherwise
350      */
351     private static <T extends DataObject> boolean deleteIfExists(final InstanceIdentifier<Node> vppIid,
352         final InstanceIdentifier<T> iid, byte retryCounter) {
353         LOG.trace("Netconf DELETE transaction started. Data will be read at first. RetryCounter: {}", retryCounter);
354         Preconditions.checkNotNull(vppIid);
355         DataBroker mountpoint = VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey();
356         final Optional<T> optionalObject = read(vppIid, LogicalDatastoreType.CONFIGURATION, iid, RETRY_COUNT);
357         if (!optionalObject.isPresent()) {
358             LOG.warn("Netconf DELETE transaction aborted. Data to remove are not present or cannot be read. Iid: {}",
359                 iid);
360             // Return true, this state is not considered as an error
361             return true;
362         }
363         final ReadWriteTransaction rwTx = mountpoint.newReadWriteTransaction();
364         try {
365             rwTx.delete(LogicalDatastoreType.CONFIGURATION, iid);
366             final CheckedFuture<Void, TransactionCommitFailedException> futureTask = rwTx.submit();
367             futureTask.get();
368             LOG.trace("Netconf DELETE transaction done for {}", iid);
369             return true;
370         } catch (Exception e) {
371             // Retry
372             if (retryCounter > 0) {
373                 LOG.warn("Netconf DELETE transaction failed to {}. Restarting transaction ... ", e.getMessage());
374                 return deleteIfExists(vppIid, iid, --retryCounter);
375             } else {
376                 LOG.warn("Netconf DELETE transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
377                 return false;
378             }
379         }
380     }
381
382     private static <T extends DataObject> boolean deleteIfExists(final InstanceIdentifier<Node> vppIid,
383             final Set<InstanceIdentifier<T>> iids, byte retryCounter) {
384         LOG.trace("Netconf DELETE transaction started. Data will be read at first. RetryCounter: {}", retryCounter);
385         Preconditions.checkNotNull(vppIid);
386         final ReadWriteTransaction rwTx = VbdNetconfTransaction.NODE_DATA_BROKER_MAP.get(vppIid).getKey().newReadWriteTransaction();
387         Set<InstanceIdentifier<T>> alreadyRemoved = new HashSet<>();
388         for (InstanceIdentifier<T> iid : iids) {
389             short microReadRetries = 3;
390             while (microReadRetries > 0) {
391                 try {
392                     if (rwTx.read(LogicalDatastoreType.CONFIGURATION, iid).get().isPresent()) {
393                         rwTx.delete(LogicalDatastoreType.CONFIGURATION, iid);
394                     } else {
395                         LOG.warn("Node {} does not exist. It won't be removed.", iid.getPathArguments());
396                         alreadyRemoved.add(iid);
397                     }
398                     break;
399                 } catch (InterruptedException | ExecutionException e) {
400                     LOG.warn("Failed to read {}. Retrying... ", iid.getPathArguments());
401                     microReadRetries--;
402                 }
403             }
404         }
405         alreadyRemoved.forEach(t -> {
406             iids.remove(t);
407         });
408         try {
409             final CheckedFuture<Void, TransactionCommitFailedException> futureTask = rwTx.submit();
410             futureTask.get();
411             LOG.trace("Netconf DELETE transaction done for {}", iids);
412             return true;
413         } catch (Exception e) {
414             // Retry
415             if (retryCounter > 0) {
416                 LOG.warn("Netconf DELETE transaction failed to {}. Restarting transaction ... ", e.getMessage());
417                 return deleteIfExists(vppIid, iids, --retryCounter);
418             } else {
419                 LOG.warn("Netconf DELETE transaction unsuccessful. Maximal number of attempts reached. Trace: {}", e);
420                 return false;
421             }
422         }
423     }
424 }