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