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