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