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