8af6870843f145ef581e16931606e92d0430683d
[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                                                     final BigInteger dpnId = intfDpnId;
161                                                     final int ifIndex = intrf.getIfIndex();
162                                                     LOG.info("VPN Interface add event - intfName {} onto vpnName {}"
163                                                             + " running oper-driven", vpnIf.getName(), vpnName);
164                                                     Set<String> prefixes = new HashSet<>();
165                                                     vpnInterfaceManager.processVpnInterfaceUp(dpnId, vpnIf, primaryRd,
166                                                             ifIndex, false, writeConfigTxn, writeOperTxn, writeInvTxn,
167                                                             intrf, vpnName, prefixes);
168                                                     mapOfRdAndPrefixesForRefreshFib.put(primaryRd, prefixes);
169                                                 }
170                                             }
171
172                                         }
173                                     });
174                                 futures.add(operFuture);
175                                 operFuture.get(); //Synchronous submit of operTxn
176                             });
177                         Futures.addCallback(configFuture,
178                                 new VpnInterfaceCallBackHandler(mapOfRdAndPrefixesForRefreshFib),
179                                 MoreExecutors.directExecutor());
180                         futures.add(configFuture);
181                         //TODO: Allow immediateFailedFuture from writeCfgTxn to cancel writeInvTxn as well.
182                         Futures.addCallback(configFuture, new PostVpnInterfaceThreadWorker(intrf.getName(), true,
183                                 "Operational"));
184                     }));
185                     return futures;
186                 });
187             }
188         } catch (Exception e) {
189             LOG.error("Exception caught in Interface {} Operational State Up event", intrf.getName(), e);
190         }
191     }
192
193     @Override
194     // TODO Clean up the exception handling
195     @SuppressWarnings("checkstyle:IllegalCatch")
196     protected void remove(InstanceIdentifier<Interface> identifier, Interface intrf) {
197         final String ifName = intrf.getName();
198         BigInteger dpId = BigInteger.ZERO;
199         try {
200             if (L2vlan.class.equals(intrf.getType())) {
201                 LOG.info("VPN Interface remove event - intfName {} from InterfaceStateChangeListener",
202                                 intrf.getName());
203                 try {
204                     dpId = InterfaceUtils.getDpIdFromInterface(intrf);
205                 } catch (Exception e) {
206                     LOG.error("Unable to retrieve dpnId from interface operational data store for interface"
207                             + " {}. Fetching from vpn interface op data store. ", ifName, e);
208                 }
209                 final BigInteger inputDpId = dpId;
210                 jobCoordinator.enqueueJob("VPNINTERFACE-" + ifName, () -> {
211                     List<ListenableFuture<Void>> futures = new ArrayList<>(3);
212                     ListenableFuture<Void> configFuture =
213                         txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION,
214                             writeConfigTxn -> futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL,
215                                 writeOperTxn -> futures.add(
216                                     txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION, writeInvTxn -> {
217                                         VpnInterface cfgVpnInterface =
218                                             vpnUtil.getConfiguredVpnInterface(ifName);
219                                         if (cfgVpnInterface == null) {
220                                             LOG.debug("Interface {} is not a vpninterface, ignoring.", ifName);
221                                             return;
222                                         }
223                                         for (VpnInstanceNames vpnInterfaceVpnInstance :
224                                                 cfgVpnInterface.nonnullVpnInstanceNames()) {
225                                             String vpnName = vpnInterfaceVpnInstance.getVpnName();
226                                             Optional<VpnInterfaceOpDataEntry> optVpnInterface =
227                                                 vpnUtil.getVpnInterfaceOpDataEntry(ifName, vpnName);
228                                             if (!optVpnInterface.isPresent()) {
229                                                 LOG.debug("Interface {} vpn {} is not a vpninterface, or deletion"
230                                                     + " triggered by northbound agent. ignoring.", ifName, vpnName);
231                                                 continue;
232                                             }
233                                             final VpnInterfaceOpDataEntry vpnInterface = optVpnInterface.get();
234                                             String gwMac = intrf.getPhysAddress() != null ? intrf.getPhysAddress()
235                                                 .getValue() : vpnInterface.getGatewayMacAddress();
236                                             BigInteger dpnId = inputDpId;
237                                             if (dpnId == null || dpnId.equals(BigInteger.ZERO)) {
238                                                 dpnId = vpnInterface.getDpnId();
239                                             }
240                                             final int ifIndex = intrf.getIfIndex();
241                                             LOG.info("VPN Interface remove event - intfName {} onto vpnName {}"
242                                                 + " running oper-driver", vpnInterface.getName(), vpnName);
243                                             vpnInterfaceManager.processVpnInterfaceDown(dpnId, ifName, ifIndex, gwMac,
244                                                 vpnInterface, false, writeConfigTxn, writeOperTxn, writeInvTxn);
245                                         }
246                                     })))));
247                     futures.add(configFuture);
248                     Futures.addCallback(configFuture, new PostVpnInterfaceThreadWorker(intrf.getName(), false,
249                             "Operational"));
250                     return futures;
251                 }, DJC_MAX_RETRIES);
252             }
253         } catch (Exception e) {
254             LOG.error("Exception observed in handling deletion of VPN Interface {}. ", ifName, e);
255         }
256     }
257
258     // TODO Clean up the exception handling
259     @SuppressWarnings("checkstyle:IllegalCatch")
260     @Override
261     protected void update(InstanceIdentifier<Interface> identifier,
262                     Interface original, Interface update) {
263         final String ifName = update.getName();
264         try {
265             if (update.getIfIndex() == null) {
266                 return;
267             }
268             if (L2vlan.class.equals(update.getType())) {
269                 LOG.info("VPN Interface update event - intfName {} from InterfaceStateChangeListener",
270                         update.getName());
271                 jobCoordinator.enqueueJob("VPNINTERFACE-" + ifName, () -> {
272                     List<ListenableFuture<Void>> futures = new ArrayList<>(3);
273                     futures.add(txRunner.callWithNewWriteOnlyTransactionAndSubmit(OPERATIONAL, writeOperTxn -> {
274                         //map of prefix and vpn name used, as entry in prefix-to-interface datastore
275                         // is prerequisite for refresh Fib to avoid race condition leading to missing remote
276                         // next hop in bucket actions on bgp-vpn delete
277                         Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib = new HashMap<>();
278                         ListenableFuture<Void> configTxFuture =
279                                 txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, writeConfigTxn ->
280                                     futures.add(txRunner.callWithNewReadWriteTransactionAndSubmit(CONFIGURATION,
281                                         writeInvTxn -> {
282                                             final VpnInterface vpnIf = vpnUtil.getConfiguredVpnInterface(ifName);
283                                             if (vpnIf != null) {
284                                                 final int ifIndex = update.getIfIndex();
285                                                 BigInteger dpnId;
286                                                 try {
287                                                     dpnId = InterfaceUtils.getDpIdFromInterface(update);
288                                                 } catch (Exception e) {
289                                                     LOG.error("remove: Unable to retrieve dpnId for interface {}",
290                                                         ifName, e);
291                                                     return;
292                                                 }
293                                                 IntfTransitionState state = getTransitionState(
294                                                         original.getOperStatus(), update.getOperStatus());
295                                                 if (state.equals(IntfTransitionState.STATE_IGNORE)) {
296                                                     LOG.info("InterfaceStateChangeListener: Interface {} state "
297                                                          + "original {}" + "updated {} not handled", ifName,
298                                                          original.getOperStatus(), update.getOperStatus());
299                                                     return;
300                                                 }
301                                                 if (state.equals(IntfTransitionState.STATE_UP)
302                                                         && vpnIf.getVpnInstanceNames() != null) {
303                                                     for (VpnInstanceNames vpnInterfaceVpnInstance :
304                                                             vpnIf.getVpnInstanceNames()) {
305                                                         String vpnName = vpnInterfaceVpnInstance.getVpnName();
306                                                         String primaryRd = vpnUtil.getPrimaryRd(vpnName);
307                                                         Set<String> prefixes = new HashSet<>();
308                                                         if (!vpnInterfaceManager.isVpnInstanceReady(vpnName)) {
309                                                             LOG.error("VPN Interface update event - intfName {} "
310                                                                 + "onto vpnName {} running oper-driven UP, "
311                                                                 + "VpnInstance not ready, holding on",
312                                                                 vpnIf.getName(), vpnName);
313                                                         } else if (vpnUtil.isVpnPendingDelete(primaryRd)) {
314                                                             LOG.error("update: Ignoring UP event for vpnInterface "
315                                                                 + "{}, as vpnInstance {} with primaryRd {} is "
316                                                                 + "already marked for deletion ",
317                                                                 vpnIf.getName(), vpnName, primaryRd);
318                                                         } else {
319                                                             vpnInterfaceManager.processVpnInterfaceUp(dpnId, vpnIf,
320                                                                 primaryRd, ifIndex, true, writeConfigTxn,
321                                                                 writeOperTxn, writeInvTxn, update, vpnName, prefixes);
322                                                             mapOfRdAndPrefixesForRefreshFib.put(primaryRd, prefixes);
323                                                         }
324                                                     }
325                                                 } else if (state.equals(IntfTransitionState.STATE_DOWN)
326                                                         && vpnIf.getVpnInstanceNames() != null) {
327                                                     for (VpnInstanceNames vpnInterfaceVpnInstance :
328                                                             vpnIf.getVpnInstanceNames()) {
329                                                         String vpnName = vpnInterfaceVpnInstance.getVpnName();
330                                                         LOG.info("VPN Interface update event - intfName {} "
331                                                             + " onto vpnName {} running oper-driven DOWN",
332                                                             vpnIf.getName(), vpnName);
333                                                         Optional<VpnInterfaceOpDataEntry> optVpnInterface = vpnUtil
334                                                             .getVpnInterfaceOpDataEntry(vpnIf.getName(), vpnName);
335                                                         if (optVpnInterface.isPresent()) {
336                                                             VpnInterfaceOpDataEntry vpnOpInterface =
337                                                                 optVpnInterface.get();
338                                                             vpnInterfaceManager.processVpnInterfaceDown(dpnId,
339                                                                 vpnIf.getName(), ifIndex, update.getPhysAddress()
340                                                                 .getValue(), vpnOpInterface, true,
341                                                                 writeConfigTxn, writeOperTxn, writeInvTxn);
342                                                         } else {
343                                                             LOG.error("InterfaceStateChangeListener Update DOWN - "
344                                                                 + " vpnInterface {}not available, ignoring event",
345                                                                 vpnIf.getName());
346                                                             continue;
347                                                         }
348                                                     }
349                                                 }
350                                             } else {
351                                                 LOG.debug("Interface {} is not a vpninterface, ignoring.", ifName);
352                                             }
353                                         })));
354                         Futures.addCallback(configTxFuture,
355                             new VpnInterfaceCallBackHandler(mapOfRdAndPrefixesForRefreshFib),
356                             MoreExecutors.directExecutor());
357                         futures.add(configTxFuture);
358                     }));
359                     return futures;
360                 });
361             }
362         } catch (Exception e) {
363             LOG.error("Exception observed in handling updation of VPN Interface {}. ", update.getName(), e);
364         }
365     }
366
367     private class PostVpnInterfaceThreadWorker implements FutureCallback<Void> {
368         private final String interfaceName;
369         private final boolean add;
370         private final String txnDestination;
371
372         PostVpnInterfaceThreadWorker(String interfaceName, boolean add, String transactionDest) {
373             this.interfaceName = interfaceName;
374             this.add = add;
375             this.txnDestination = transactionDest;
376         }
377
378         @Override
379         public void onSuccess(Void voidObj) {
380             if (add) {
381                 LOG.debug("InterfaceStateChangeListener: VrfEntries for {} stored into destination {} successfully",
382                         interfaceName, txnDestination);
383             } else {
384                 LOG.debug("InterfaceStateChangeListener:  VrfEntries for {} removed successfully", interfaceName);
385             }
386         }
387
388         @Override
389         public void onFailure(Throwable throwable) {
390             if (add) {
391                 LOG.error("InterfaceStateChangeListener: VrfEntries for {} failed to store into destination {}",
392                         interfaceName, txnDestination, throwable);
393             } else {
394                 LOG.error("InterfaceStateChangeListener: VrfEntries for {} removal failed", interfaceName, throwable);
395                 vpnUtil.unsetScheduledToRemoveForVpnInterface(interfaceName);
396             }
397         }
398     }
399
400     private IntfTransitionState getTransitionState(Interface.OperStatus original , Interface.OperStatus updated) {
401         IntfTransitionState transitionState = stateTable.get(original, updated);
402
403         if (transitionState == null) {
404             return IntfTransitionState.STATE_IGNORE;
405         }
406         return transitionState;
407     }
408
409     private class VpnInterfaceCallBackHandler implements FutureCallback<Void> {
410         private final Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib;
411
412         VpnInterfaceCallBackHandler(Map<String, Set<String>> mapOfRdAndPrefixesForRefreshFib) {
413             this.mapOfRdAndPrefixesForRefreshFib = mapOfRdAndPrefixesForRefreshFib;
414         }
415
416         @Override
417         public void onSuccess(Void voidObj) {
418             mapOfRdAndPrefixesForRefreshFib.forEach((primaryRd, prefixes) -> {
419                 prefixes.forEach(prefix -> {
420                     fibManager.refreshVrfEntry(primaryRd, prefix);
421                 });
422             });
423         }
424
425         @Override
426         public void onFailure(Throwable throwable) {
427             LOG.debug("write Tx config operation failed {}", throwable);
428         }
429     }
430 }