Use managed transactions in vpnmanager-impl
[netvirt.git] / vpnmanager / impl / src / main / java / org / opendaylight / netvirt / vpnmanager / VpnOpStatusListener.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 com.google.common.base.Optional;
11 import com.google.common.util.concurrent.FutureCallback;
12 import com.google.common.util.concurrent.Futures;
13 import com.google.common.util.concurrent.ListenableFuture;
14 import com.google.common.util.concurrent.MoreExecutors;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18 import javax.annotation.PostConstruct;
19 import javax.inject.Inject;
20 import javax.inject.Singleton;
21 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.genius.datastoreutils.AsyncDataTreeChangeListenerBase;
24 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
25 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
26 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
27 import org.opendaylight.genius.utils.SystemPropertyReader;
28 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
29 import org.opendaylight.netvirt.bgpmanager.api.IBgpManager;
30 import org.opendaylight.netvirt.fibmanager.api.IFibManager;
31 import org.opendaylight.netvirt.vpnmanager.api.VpnExtraRouteHelper;
32 import org.opendaylight.yang.gen.v1.urn.ericsson.params.xml.ns.yang.ebgp.rev150901.AddressFamily;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3nexthop.rev150409.L3nexthop;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3nexthop.rev150409.l3nexthop.VpnNexthops;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3nexthop.rev150409.l3nexthop.VpnNexthopsKey;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.VpnInstanceOpData;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.prefix.to._interface.VpnIds;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntry;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpntargets.VpnTarget;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.to.extraroutes.Vpn;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 @Singleton
47 public class VpnOpStatusListener extends AsyncDataTreeChangeListenerBase<VpnInstanceOpDataEntry, VpnOpStatusListener> {
48     private static final Logger LOG = LoggerFactory.getLogger(VpnOpStatusListener.class);
49     private final DataBroker dataBroker;
50     private final ManagedNewTransactionRunner txRunner;
51     private final IBgpManager bgpManager;
52     private final IdManagerService idManager;
53     private final IFibManager fibManager;
54     private final IMdsalApiManager mdsalManager;
55     private final VpnFootprintService vpnFootprintService;
56     private final JobCoordinator jobCoordinator;
57
58     @Inject
59     public VpnOpStatusListener(final DataBroker dataBroker, final IBgpManager bgpManager,
60                                final IdManagerService idManager, final IFibManager fibManager,
61                                final IMdsalApiManager mdsalManager, final VpnFootprintService vpnFootprintService,
62                                final JobCoordinator jobCoordinator) {
63         super(VpnInstanceOpDataEntry.class, VpnOpStatusListener.class);
64         this.dataBroker = dataBroker;
65         this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
66         this.bgpManager = bgpManager;
67         this.idManager = idManager;
68         this.fibManager = fibManager;
69         this.mdsalManager = mdsalManager;
70         this.vpnFootprintService = vpnFootprintService;
71         this.jobCoordinator = jobCoordinator;
72     }
73
74     @PostConstruct
75     public void start() {
76         LOG.info("{} start", getClass().getSimpleName());
77         registerListener(LogicalDatastoreType.OPERATIONAL, dataBroker);
78     }
79
80     @Override
81     protected InstanceIdentifier<VpnInstanceOpDataEntry> getWildCardPath() {
82         return InstanceIdentifier.create(VpnInstanceOpData.class).child(VpnInstanceOpDataEntry.class);
83     }
84
85     @Override
86     protected VpnOpStatusListener getDataTreeChangeListener() {
87         return VpnOpStatusListener.this;
88     }
89
90     @Override
91     protected void remove(InstanceIdentifier<VpnInstanceOpDataEntry> identifier, VpnInstanceOpDataEntry value) {
92         LOG.info("remove: Ignoring vpn Op {} with rd {}", value.getVpnInstanceName(), value.getVrfId());
93     }
94
95     @Override
96     @SuppressWarnings("checkstyle:IllegalCatch")
97     protected void update(InstanceIdentifier<VpnInstanceOpDataEntry> identifier,
98                           VpnInstanceOpDataEntry original, VpnInstanceOpDataEntry update) {
99         LOG.info("update: Processing update for vpn {} with rd {}", update.getVpnInstanceName(), update.getVrfId());
100         if (update.getVpnState() == VpnInstanceOpDataEntry.VpnState.PendingDelete
101                 && vpnFootprintService.isVpnFootPrintCleared(update)) {
102             //Cleanup VPN data
103             final String vpnName = update.getVpnInstanceName();
104             final List<String> rds = update.getRd();
105             String primaryRd = update.getVrfId();
106             final long vpnId = VpnUtil.getVpnId(dataBroker, vpnName);
107             jobCoordinator.enqueueJob("VPN-" + update.getVpnInstanceName(), () -> {
108                 // Two transactions are used, one for operational, one for config; we only submit the config
109                 // transaction if the operational transaction succeeds
110                 ListenableFuture<Void> operationalFuture = txRunner.callWithNewWriteOnlyTransactionAndSubmit(operTx -> {
111                     // Clean up VPNExtraRoutes Operational DS
112                     if (VpnUtil.isBgpVpn(vpnName, primaryRd)) {
113                         if (update.getType() == VpnInstanceOpDataEntry.Type.L2) {
114                             rds.parallelStream().forEach(rd -> bgpManager.deleteVrf(rd, false, AddressFamily.L2VPN));
115                         }
116                         if (update.isIpv4Configured()) {
117                             rds.parallelStream().forEach(rd -> bgpManager.deleteVrf(rd, false, AddressFamily.IPV4));
118                         }
119                         if (update.isIpv6Configured()) {
120                             rds.parallelStream().forEach(rd -> bgpManager.deleteVrf(rd, false, AddressFamily.IPV6));
121                         }
122                     }
123                     InstanceIdentifier<Vpn> vpnToExtraroute =
124                             VpnExtraRouteHelper.getVpnToExtrarouteVpnIdentifier(vpnName);
125                     Optional<Vpn> optVpnToExtraroute = VpnUtil.read(dataBroker,
126                             LogicalDatastoreType.OPERATIONAL, vpnToExtraroute);
127                     if (optVpnToExtraroute.isPresent()) {
128                         VpnUtil.removeVpnExtraRouteForVpn(vpnName, operTx);
129                     }
130
131                     if (VpnUtil.isL3VpnOverVxLan(update.getL3vni())) {
132                         VpnUtil.removeExternalTunnelDemuxFlows(vpnName, dataBroker, mdsalManager);
133                     }
134
135                     // Clean up PrefixToInterface Operational DS
136                     Optional<VpnIds> optPrefixToIntf = VpnUtil.read(dataBroker, LogicalDatastoreType.OPERATIONAL,
137                             VpnUtil.getPrefixToInterfaceIdentifier(vpnId));
138                     if (optPrefixToIntf.isPresent()) {
139                         VpnUtil.removePrefixToInterfaceForVpnId(vpnId, operTx);
140                     }
141
142                     // Clean up L3NextHop Operational DS
143                     InstanceIdentifier<VpnNexthops> vpnNextHops = InstanceIdentifier.builder(L3nexthop.class).child(
144                             VpnNexthops.class, new VpnNexthopsKey(vpnId)).build();
145                     Optional<VpnNexthops> optL3nexthopForVpnId = VpnUtil.read(dataBroker,
146                             LogicalDatastoreType.OPERATIONAL,
147                             vpnNextHops);
148                     if (optL3nexthopForVpnId.isPresent()) {
149                         VpnUtil.removeL3nexthopForVpnId(vpnId, operTx);
150                     }
151
152                     // Clean up VPNInstanceOpDataEntry
153                     VpnUtil.removeVpnOpInstance(primaryRd, operTx);
154                 });
155
156                 Futures.addCallback(operationalFuture, new FutureCallback<Void>() {
157                     @Override
158                     public void onSuccess(Void result) {
159                         Futures.addCallback(txRunner.callWithNewWriteOnlyTransactionAndSubmit(confTx -> {
160                             // Clean up VpnInstanceToVpnId from Config DS
161                             VpnUtil.removeVpnIdToVpnInstance(vpnId, confTx);
162                             VpnUtil.removeVpnInstanceToVpnId(vpnName, confTx);
163                             LOG.trace("Removed vpnIdentifier for  rd{} vpnname {}", primaryRd, vpnName);
164
165                             // Clean up FIB Entries Config DS
166                             synchronized (vpnName.intern()) {
167                                 fibManager.removeVrfTable(primaryRd, confTx);
168                             }
169                         }), new VpnOpStatusListener.PostDeleteVpnInstanceWorker(vpnName),
170                                 MoreExecutors.directExecutor());
171                         // Note: Release the of VpnId will happen in PostDeleteVpnInstancWorker only if
172                         // operationalTxn/Config succeeds.
173                     }
174
175                     @Override
176                     public void onFailure(Throwable throwable) {
177                         LOG.error("Error deleting VPN {}", vpnName, throwable);
178                     }
179                 }, MoreExecutors.directExecutor());
180
181                 LOG.info("Removed vpn data for vpnname {}", vpnName);
182                 return Collections.singletonList(operationalFuture);
183             }, SystemPropertyReader.getDataStoreJobCoordinatorMaxRetries());
184         } else if (update.getVpnState() == VpnInstanceOpDataEntry.VpnState.Created) {
185             final String vpnName = update.getVpnInstanceName();
186             final List<String> rds = update.getRd();
187             String primaryRd = update.getVrfId();
188             if (!VpnUtil.isBgpVpn(vpnName, primaryRd)) {
189                 return;
190             }
191             if (original == null) {
192                 LOG.error("VpnOpStatusListener.update: vpn {} with RD {}. add() handler already called",
193                        vpnName, primaryRd);
194                 return;
195             }
196             if (update.getVpnTargets() == null) {
197                 LOG.error("VpnOpStatusListener.update: vpn {} with RD {} vpnTargets not ready",
198                        vpnName, primaryRd);
199                 return;
200             }
201             List<VpnTarget> vpnTargetList = update.getVpnTargets().getVpnTarget();
202             List<String> ertList = new ArrayList<>();
203             List<String> irtList = new ArrayList<>();
204             if (vpnTargetList != null) {
205                 for (VpnTarget vpnTarget : vpnTargetList) {
206                     if (vpnTarget.getVrfRTType() == VpnTarget.VrfRTType.ExportExtcommunity) {
207                         ertList.add(vpnTarget.getVrfRTValue());
208                     }
209                     if (vpnTarget.getVrfRTType() == VpnTarget.VrfRTType.ImportExtcommunity) {
210                         irtList.add(vpnTarget.getVrfRTValue());
211                     }
212                     if (vpnTarget.getVrfRTType() == VpnTarget.VrfRTType.Both) {
213                         ertList.add(vpnTarget.getVrfRTValue());
214                         irtList.add(vpnTarget.getVrfRTValue());
215                     }
216                 }
217             } else {
218                 LOG.error("VpnOpStatusListener.update: vpn target list is empty, cannot add BGP"
219                       + " VPN {} RD {}", vpnName, primaryRd);
220                 return;
221             }
222             jobCoordinator.enqueueJob("VPN-" + update.getVpnInstanceName(), () -> {
223                 rds.parallelStream().forEach(rd -> {
224                     try {
225                         LOG.info("VpnOpStatusListener.update: updating BGPVPN for vpn {} with RD {}"
226                                 + " Type is {}, IPv4 is {}, IPv6 is {}", vpnName, primaryRd, update.getType(),
227                                 update.isIpv4Configured(), update.isIpv6Configured());
228                         if (update.getType() == VpnInstanceOpDataEntry.Type.L2) {
229                             bgpManager.addVrf(rd, irtList, ertList, AddressFamily.L2VPN);
230                         } else {
231                             bgpManager.deleteVrf(rd, false, AddressFamily.L2VPN);
232                         }
233                         if (!original.isIpv4Configured() && update.isIpv4Configured()) {
234                             bgpManager.addVrf(rd, irtList, ertList, AddressFamily.IPV4);
235                         } else if (original.isIpv4Configured() && !update.isIpv4Configured()) {
236                             bgpManager.deleteVrf(rd, false, AddressFamily.IPV4);
237                         }
238                         if (!original.isIpv6Configured() && update.isIpv6Configured()) {
239                             bgpManager.addVrf(rd, irtList, ertList, AddressFamily.IPV6);
240                         } else if (original.isIpv6Configured() && !update.isIpv6Configured()) {
241                             bgpManager.deleteVrf(rd, false, AddressFamily.IPV6);
242                         }
243                     } catch (Exception e) {
244                         LOG.error("VpnOpStatusListener.update: Exception when updating VRF to BGP"
245                                + " for vpn {} rd {}", vpnName, rd);
246                     }
247                 });
248                 return Collections.emptyList();
249             });
250         }
251     }
252
253     @Override
254     protected void add(final InstanceIdentifier<VpnInstanceOpDataEntry> identifier,
255                        final VpnInstanceOpDataEntry value) {
256         LOG.debug("add: Ignoring vpn Op {} with rd {}", value.getVpnInstanceName(), value.getVrfId());
257     }
258
259     private class PostDeleteVpnInstanceWorker implements FutureCallback<Void> {
260         private final Logger log = LoggerFactory.getLogger(VpnOpStatusListener.PostDeleteVpnInstanceWorker.class);
261         String vpnName;
262
263         PostDeleteVpnInstanceWorker(String vpnName)  {
264             this.vpnName = vpnName;
265         }
266
267         /**
268          * This implies that all the future instances have returned success.
269          * Release the ID used for VPN back to IdManager
270          */
271         @Override
272         public void onSuccess(Void ignored) {
273             VpnUtil.releaseId(idManager, VpnConstants.VPN_IDPOOL_NAME, vpnName);
274             log.info("onSuccess: VpnId for VpnName {} is released to IdManager successfully.", vpnName);
275         }
276
277         /**
278          * This method is used to handle failure callbacks.
279          */
280         @Override
281         public void onFailure(Throwable throwable) {
282             log.error("onFailure: Job for vpnInstance: {} failed with exception:",
283                       vpnName , throwable);
284         }
285     }
286 }