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