Enable troubleshooting for fib and subnetroute
[netvirt.git] / vpnmanager / impl / src / main / java / org / opendaylight / netvirt / vpnmanager / InterfaceStateChangeListener.java
1 /*
2  * Copyright (c) 2015, 2017 Ericsson India Global Services Pvt Ltd. 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.netvirt.vpnmanager;
9
10 import static org.opendaylight.genius.infra.Datastore.CONFIGURATION;
11 import static org.opendaylight.genius.infra.Datastore.OPERATIONAL;
12
13 import com.google.common.base.Optional;
14 import com.google.common.collect.HashBasedTable;
15 import com.google.common.collect.Table;
16 import com.google.common.util.concurrent.FutureCallback;
17 import com.google.common.util.concurrent.Futures;
18 import com.google.common.util.concurrent.ListenableFuture;
19 import com.google.common.util.concurrent.MoreExecutors;
20 import java.math.BigInteger;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import javax.annotation.PostConstruct;
28 import javax.inject.Inject;
29 import javax.inject.Singleton;
30 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
31 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
32 import org.opendaylight.genius.datastoreutils.AsyncDataTreeChangeListenerBase;
33 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
34 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
35 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
36 import org.opendaylight.netvirt.fibmanager.api.IFibManager;
37 import org.opendaylight.netvirt.vpnmanager.api.InterfaceUtils;
38 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterface;
39 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.vpn._interface.VpnInstanceNames;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev170119.L2vlan;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfacesState;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface.OperStatus;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn._interface.op.data.VpnInterfaceOpDataEntry;
45 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 @Singleton
50 public class InterfaceStateChangeListener
51     extends AsyncDataTreeChangeListenerBase<Interface, InterfaceStateChangeListener> {
52
53     private static final Logger LOG = LoggerFactory.getLogger(InterfaceStateChangeListener.class);
54     private static final short DJC_MAX_RETRIES = 3;
55     private final DataBroker dataBroker;
56     private final ManagedNewTransactionRunner txRunner;
57     private final VpnInterfaceManager vpnInterfaceManager;
58     private final VpnUtil vpnUtil;
59     private final JobCoordinator jobCoordinator;
60     private final IFibManager fibManager;
61
62     Table<OperStatus, OperStatus, IntfTransitionState> stateTable = HashBasedTable.create();
63
64     enum IntfTransitionState {
65         STATE_UP,
66         STATE_DOWN,
67         STATE_IGNORE
68     }
69
70     private void initialize() {
71         //  Interface State Transition Table
72         //               Up                Down            Unknown
73         // ---------------------------------------------------------------
74         /* Up       { STATE_IGNORE,   STATE_DOWN,     STATE_IGNORE }, */
75         /* Down     { STATE_UP,       STATE_IGNORE,   STATE_IGNORE }, */
76         /* Unknown  { STATE_UP,       STATE_DOWN,     STATE_IGNORE }, */
77
78         stateTable.put(Interface.OperStatus.Up, Interface.OperStatus.Down, IntfTransitionState.STATE_DOWN);
79         stateTable.put(Interface.OperStatus.Down, Interface.OperStatus.Up, IntfTransitionState.STATE_UP);
80         stateTable.put(Interface.OperStatus.Unknown, Interface.OperStatus.Up, IntfTransitionState.STATE_UP);
81         stateTable.put(Interface.OperStatus.Unknown, Interface.OperStatus.Down, IntfTransitionState.STATE_DOWN);
82     }
83
84     @Inject
85     public InterfaceStateChangeListener(final DataBroker dataBroker, final VpnInterfaceManager vpnInterfaceManager,
86             final VpnUtil vpnUtil, final JobCoordinator jobCoordinator, final IFibManager fibManager) {
87         super(Interface.class, InterfaceStateChangeListener.class);
88         this.dataBroker = dataBroker;
89         this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
90         this.vpnInterfaceManager = vpnInterfaceManager;
91         this.vpnUtil = vpnUtil;
92         this.jobCoordinator = jobCoordinator;
93         this.fibManager = fibManager;
94         initialize();
95     }
96
97     @PostConstruct
98     public void start() {
99         LOG.info("{} start", getClass().getSimpleName());
100         registerListener(LogicalDatastoreType.OPERATIONAL, dataBroker);
101     }
102
103
104     @Override
105     protected InstanceIdentifier<Interface> getWildCardPath() {
106         return InstanceIdentifier.create(InterfacesState.class).child(Interface.class);
107     }
108
109     @Override
110     protected InterfaceStateChangeListener getDataTreeChangeListener() {
111         return InterfaceStateChangeListener.this;
112     }
113
114
115     @Override
116     // TODO Clean up the exception handling
117     @SuppressWarnings("checkstyle:IllegalCatch")
118     protected void add(InstanceIdentifier<Interface> identifier, Interface intrf) {
119         try {
120             if (L2vlan.class.equals(intrf.getType())) {
121                 LOG.info("VPN Interface add event - intfName {} from InterfaceStateChangeListener",
122                                 intrf.getName());
123                 jobCoordinator.enqueueJob("VPNINTERFACE-" + intrf.getName(), () -> {
124                     List<ListenableFuture<Void>> futures = new ArrayList<>(3);
125                     futures.add(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION, writeInvTxn -> {
126                         //map of prefix and vpn name used, as entry in prefix-to-interface datastore
127                         // is prerequisite for refresh Fib to avoid race condition leading to missing remote next hop
128                         // in bucket actions on bgp-vpn delete
129                         Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib = new HashMap<>();
130                         ListenableFuture<Void> configFuture
131                             = txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, writeConfigTxn -> {
132                                 ListenableFuture<Void> operFuture
133                                     = txRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeOperTxn -> {
134                                         final String interfaceName = intrf.getName();
135                                         LOG.info("Detected interface add event for interface {}", interfaceName);
136                                         final VpnInterface vpnIf = vpnUtil.getConfiguredVpnInterface(interfaceName);
137                                         if (vpnIf != null) {
138                                             for (VpnInstanceNames vpnInterfaceVpnInstance :
139                                                     vpnIf.nonnullVpnInstanceNames()) {
140                                                 String vpnName = vpnInterfaceVpnInstance.getVpnName();
141                                                 String primaryRd = vpnUtil.getPrimaryRd(vpnName);
142                                                 if (!vpnInterfaceManager.isVpnInstanceReady(vpnName)) {
143                                                     LOG.info("VPN Interface add event - intfName {} onto vpnName {} "
144                                                             + "running oper-driven, VpnInstance not ready, holding"
145                                                             + " on", vpnIf.getName(), vpnName);
146                                                 } else if (vpnUtil.isVpnPendingDelete(primaryRd)) {
147                                                     LOG.error("add: Ignoring addition of vpnInterface {}, as"
148                                                             + " vpnInstance {} with primaryRd {} is already marked for"
149                                                             + " deletion", interfaceName, vpnName, primaryRd);
150                                                 } else {
151                                                     BigInteger intfDpnId = BigInteger.ZERO;
152                                                     try {
153                                                         intfDpnId = InterfaceUtils.getDpIdFromInterface(intrf);
154                                                     } catch (Exception e) {
155                                                         LOG.error("Unable to retrieve dpnId for interface {}. "
156                                                                 + "Process vpn interface add failed",intrf.getName(),
157                                                                 e);
158                                                         return;
159                                                     }
160                                                     LOG.error("InterfaceStateChangeListener- Processing ifState"
161                                                                     + " {} add event with dpnId {}",
162                                                             intrf.getName(), intfDpnId);
163                                                     final BigInteger dpnId = intfDpnId;
164                                                     final int ifIndex = intrf.getIfIndex();
165                                                     LOG.info("VPN Interface add event - intfName {} onto vpnName {}"
166                                                             + " running oper-driven", vpnIf.getName(), vpnName);
167                                                     Set<String> prefixes = new HashSet<>();
168                                                     vpnInterfaceManager.processVpnInterfaceUp(dpnId, vpnIf, primaryRd,
169                                                             ifIndex, false, writeConfigTxn, writeOperTxn, writeInvTxn,
170                                                             intrf, vpnName, prefixes);
171                                                     mapOfRdAndPrefixesForRefreshFib.put(primaryRd, prefixes);
172                                                 }
173                                             }
174
175                                         }
176                                     });
177                                 futures.add(operFuture);
178                                 operFuture.get(); //Synchronous submit of operTxn
179                             });
180                         Futures.addCallback(configFuture,
181                                 new VpnInterfaceCallBackHandler(mapOfRdAndPrefixesForRefreshFib),
182                                 MoreExecutors.directExecutor());
183                         futures.add(configFuture);
184                         //TODO: Allow immediateFailedFuture from writeCfgTxn to cancel writeInvTxn as well.
185                         Futures.addCallback(configFuture, new PostVpnInterfaceThreadWorker(intrf.getName(), true,
186                                 "Operational"), MoreExecutors.directExecutor());
187                     }));
188                     return futures;
189                 });
190             }
191         } catch (Exception e) {
192             LOG.error("Exception caught in Interface {} Operational State Up event", intrf.getName(), e);
193         }
194     }
195
196     @Override
197     // TODO Clean up the exception handling
198     @SuppressWarnings("checkstyle:IllegalCatch")
199     protected void remove(InstanceIdentifier<Interface> identifier, Interface intrf) {
200         final String ifName = intrf.getName();
201         BigInteger dpId = BigInteger.ZERO;
202         try {
203             if (L2vlan.class.equals(intrf.getType())) {
204                 LOG.info("VPN Interface remove event - intfName {} from InterfaceStateChangeListener",
205                                 intrf.getName());
206                 try {
207                     dpId = InterfaceUtils.getDpIdFromInterface(intrf);
208                 } catch (Exception e) {
209                     LOG.error("Unable to retrieve dpnId from interface operational data store for interface"
210                             + " {}. Fetching from vpn interface op data store. ", ifName, e);
211                 }
212                 final BigInteger inputDpId = dpId;
213                 jobCoordinator.enqueueJob("VPNINTERFACE-" + ifName, () -> {
214                     List<ListenableFuture<Void>> futures = new ArrayList<>(3);
215                     ListenableFuture<Void> configFuture =
216                         txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION,
217                             writeConfigTxn -> futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
218                                 writeOperTxn -> futures.add(
219                                     txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION, writeInvTxn -> {
220                                         VpnInterface cfgVpnInterface =
221                                             vpnUtil.getConfiguredVpnInterface(ifName);
222                                         if (cfgVpnInterface == null) {
223                                             LOG.debug("Interface {} is not a vpninterface, ignoring.", ifName);
224                                             return;
225                                         }
226                                         for (VpnInstanceNames vpnInterfaceVpnInstance :
227                                                 cfgVpnInterface.nonnullVpnInstanceNames()) {
228                                             String vpnName = vpnInterfaceVpnInstance.getVpnName();
229                                             Optional<VpnInterfaceOpDataEntry> optVpnInterface =
230                                                 vpnUtil.getVpnInterfaceOpDataEntry(ifName, vpnName);
231                                             if (!optVpnInterface.isPresent()) {
232                                                 LOG.debug("Interface {} vpn {} is not a vpninterface, or deletion"
233                                                     + " triggered by northbound agent. ignoring.", ifName, vpnName);
234                                                 continue;
235                                             }
236                                             final VpnInterfaceOpDataEntry vpnInterface = optVpnInterface.get();
237                                             String gwMac = intrf.getPhysAddress() != null ? intrf.getPhysAddress()
238                                                 .getValue() : vpnInterface.getGatewayMacAddress();
239                                             BigInteger dpnId = inputDpId;
240                                             if (dpnId == null || dpnId.equals(BigInteger.ZERO)) {
241                                                 dpnId = vpnInterface.getDpnId();
242                                             }
243                                             final int ifIndex = intrf.getIfIndex();
244                                             LOG.info("VPN Interface remove event - intfName {} onto vpnName {}"
245                                                 + " running oper-driver", vpnInterface.getName(), vpnName);
246                                             vpnInterfaceManager.processVpnInterfaceDown(dpnId, ifName, ifIndex, gwMac,
247                                                 vpnInterface, false, writeConfigTxn, writeOperTxn, writeInvTxn);
248                                         }
249                                     })))));
250                     futures.add(configFuture);
251                     Futures.addCallback(configFuture, new PostVpnInterfaceThreadWorker(intrf.getName(), false,
252                             "Operational"), MoreExecutors.directExecutor());
253                     return futures;
254                 }, DJC_MAX_RETRIES);
255             }
256         } catch (Exception e) {
257             LOG.error("Exception observed in handling deletion of VPN Interface {}. ", ifName, e);
258         }
259     }
260
261     // TODO Clean up the exception handling
262     @SuppressWarnings("checkstyle:IllegalCatch")
263     @Override
264     protected void update(InstanceIdentifier<Interface> identifier,
265                     Interface original, Interface update) {
266         final String ifName = update.getName();
267         try {
268             if (update.getIfIndex() == null) {
269                 return;
270             }
271             if (L2vlan.class.equals(update.getType())) {
272                 LOG.info("VPN Interface update event - intfName {} from InterfaceStateChangeListener",
273                         update.getName());
274                 jobCoordinator.enqueueJob("VPNINTERFACE-" + ifName, () -> {
275                     List<ListenableFuture<Void>> futures = new ArrayList<>(3);
276                     futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeOperTxn -> {
277                         //map of prefix and vpn name used, as entry in prefix-to-interface datastore
278                         // is prerequisite for refresh Fib to avoid race condition leading to missing remote
279                         // next hop in bucket actions on bgp-vpn delete
280                         Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib = new HashMap<>();
281                         ListenableFuture<Void> configTxFuture =
282                                 txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, writeConfigTxn ->
283                                     futures.add(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION,
284                                         writeInvTxn -> {
285                                             final VpnInterface vpnIf = vpnUtil.getConfiguredVpnInterface(ifName);
286                                             if (vpnIf != null) {
287                                                 final int ifIndex = update.getIfIndex();
288                                                 BigInteger dpnId;
289                                                 try {
290                                                     dpnId = InterfaceUtils.getDpIdFromInterface(update);
291                                                 } catch (Exception e) {
292                                                     LOG.error("remove: Unable to retrieve dpnId for interface {}",
293                                                         ifName, e);
294                                                     return;
295                                                 }
296                                                 IntfTransitionState state = getTransitionState(
297                                                         original.getOperStatus(), update.getOperStatus());
298                                                 if (state.equals(IntfTransitionState.STATE_IGNORE)) {
299                                                     LOG.info("InterfaceStateChangeListener: Interface {} state "
300                                                          + "original {}" + "updated {} not handled", ifName,
301                                                          original.getOperStatus(), update.getOperStatus());
302                                                     return;
303                                                 }
304                                                 LOG.error("InterfaceStateChangeListener- Processing ifState {} "
305                                                                 + "update event "
306                                                                 + "with dpnId {} operstate {}",
307                                                         ifName, dpnId, update.getOperStatus());
308                                                 if (state.equals(IntfTransitionState.STATE_UP)
309                                                         && vpnIf.getVpnInstanceNames() != null) {
310                                                     for (VpnInstanceNames vpnInterfaceVpnInstance :
311                                                             vpnIf.getVpnInstanceNames()) {
312                                                         String vpnName = vpnInterfaceVpnInstance.getVpnName();
313                                                         String primaryRd = vpnUtil.getPrimaryRd(vpnName);
314                                                         Set<String> prefixes = new HashSet<>();
315                                                         if (!vpnInterfaceManager.isVpnInstanceReady(vpnName)) {
316                                                             LOG.error("VPN Interface update event - intfName {} "
317                                                                 + "onto vpnName {} running oper-driven UP, "
318                                                                 + "VpnInstance not ready, holding on",
319                                                                 vpnIf.getName(), vpnName);
320                                                         } else if (vpnUtil.isVpnPendingDelete(primaryRd)) {
321                                                             LOG.error("update: Ignoring UP event for vpnInterface "
322                                                                 + "{}, as vpnInstance {} with primaryRd {} is "
323                                                                 + "already marked for deletion ",
324                                                                 vpnIf.getName(), vpnName, primaryRd);
325                                                         } else {
326                                                             vpnInterfaceManager.processVpnInterfaceUp(dpnId, vpnIf,
327                                                                 primaryRd, ifIndex, true, writeConfigTxn,
328                                                                 writeOperTxn, writeInvTxn, update, vpnName, prefixes);
329                                                             mapOfRdAndPrefixesForRefreshFib.put(primaryRd, prefixes);
330                                                         }
331                                                     }
332                                                 } else if (state.equals(IntfTransitionState.STATE_DOWN)
333                                                         && vpnIf.getVpnInstanceNames() != null) {
334                                                     for (VpnInstanceNames vpnInterfaceVpnInstance :
335                                                             vpnIf.getVpnInstanceNames()) {
336                                                         String vpnName = vpnInterfaceVpnInstance.getVpnName();
337                                                         LOG.info("VPN Interface update event - intfName {} "
338                                                             + " onto vpnName {} running oper-driven DOWN",
339                                                             vpnIf.getName(), vpnName);
340                                                         Optional<VpnInterfaceOpDataEntry> optVpnInterface = vpnUtil
341                                                             .getVpnInterfaceOpDataEntry(vpnIf.getName(), vpnName);
342                                                         if (optVpnInterface.isPresent()) {
343                                                             VpnInterfaceOpDataEntry vpnOpInterface =
344                                                                 optVpnInterface.get();
345                                                             vpnInterfaceManager.processVpnInterfaceDown(dpnId,
346                                                                 vpnIf.getName(), ifIndex, update.getPhysAddress()
347                                                                 .getValue(), vpnOpInterface, true,
348                                                                 writeConfigTxn, writeOperTxn, writeInvTxn);
349                                                         } else {
350                                                             LOG.error("InterfaceStateChangeListener Update DOWN - "
351                                                                 + " vpnInterface {}not available, ignoring event",
352                                                                 vpnIf.getName());
353                                                             continue;
354                                                         }
355                                                     }
356                                                 }
357                                             } else {
358                                                 LOG.debug("Interface {} is not a vpninterface, ignoring.", ifName);
359                                             }
360                                         })));
361                         Futures.addCallback(configTxFuture,
362                             new VpnInterfaceCallBackHandler(mapOfRdAndPrefixesForRefreshFib),
363                             MoreExecutors.directExecutor());
364                         futures.add(configTxFuture);
365                     }));
366                     return futures;
367                 });
368             }
369         } catch (Exception e) {
370             LOG.error("Exception observed in handling updation of VPN Interface {}. ", update.getName(), e);
371         }
372     }
373
374     private class PostVpnInterfaceThreadWorker implements FutureCallback<Void> {
375         private final String interfaceName;
376         private final boolean add;
377         private final String txnDestination;
378
379         PostVpnInterfaceThreadWorker(String interfaceName, boolean add, String transactionDest) {
380             this.interfaceName = interfaceName;
381             this.add = add;
382             this.txnDestination = transactionDest;
383         }
384
385         @Override
386         public void onSuccess(Void voidObj) {
387             if (add) {
388                 LOG.debug("InterfaceStateChangeListener: VrfEntries for {} stored into destination {} successfully",
389                         interfaceName, txnDestination);
390             } else {
391                 LOG.debug("InterfaceStateChangeListener:  VrfEntries for {} removed successfully", interfaceName);
392             }
393         }
394
395         @Override
396         public void onFailure(Throwable throwable) {
397             if (add) {
398                 LOG.error("InterfaceStateChangeListener: VrfEntries for {} failed to store into destination {}",
399                         interfaceName, txnDestination, throwable);
400             } else {
401                 LOG.error("InterfaceStateChangeListener: VrfEntries for {} removal failed", interfaceName, throwable);
402                 vpnUtil.unsetScheduledToRemoveForVpnInterface(interfaceName);
403             }
404         }
405     }
406
407     private IntfTransitionState getTransitionState(Interface.OperStatus original , Interface.OperStatus updated) {
408         IntfTransitionState transitionState = stateTable.get(original, updated);
409
410         if (transitionState == null) {
411             return IntfTransitionState.STATE_IGNORE;
412         }
413         return transitionState;
414     }
415
416     private class VpnInterfaceCallBackHandler implements FutureCallback<Void> {
417         private final Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib;
418
419         VpnInterfaceCallBackHandler(Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib) {
420             this.mapOfRdAndPrefixesForRefreshFib = mapOfRdAndPrefixesForRefreshFib;
421         }
422
423         @Override
424         public void onSuccess(Void voidObj) {
425             mapOfRdAndPrefixesForRefreshFib.forEach((primaryRd, prefixes) -> {
426                 prefixes.forEach(prefix -> {
427                     fibManager.refreshVrfEntry(primaryRd, prefix);
428                 });
429             });
430         }
431
432         @Override
433         public void onFailure(Throwable throwable) {
434             LOG.debug("write Tx config operation failedTunnelEndPointChangeListener", throwable);
435         }
436     }
437 }