Bug 6833: InterVpnLink FIB routes not populated when no VM on VPN
[netvirt.git] / vpnservice / vpnmanager / vpnmanager-impl / src / main / java / org / opendaylight / netvirt / vpnmanager / VpnFootprintService.java
1 /*
2  * Copyright (c) 2016 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
9 package org.opendaylight.netvirt.vpnmanager;
10
11 import com.google.common.base.Optional;
12 import com.google.common.util.concurrent.CheckedFuture;
13 import com.google.common.util.concurrent.FutureCallback;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.math.BigInteger;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.concurrent.ExecutionException;
20 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
21 import org.opendaylight.controller.md.sal.binding.api.NotificationPublishService;
22 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
25 import org.opendaylight.netvirt.fibmanager.api.IFibManager;
26 import org.opendaylight.netvirt.vpnmanager.utilities.InterfaceUtils;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.AddDpnEvent;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.AddDpnEventBuilder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.RemoveDpnEvent;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.RemoveDpnEventBuilder;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.add.dpn.event.AddEventData;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.add.dpn.event.AddEventDataBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.remove.dpn.event.RemoveEventData;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.remove.dpn.event.RemoveEventDataBuilder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnToDpnList;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnToDpnListBuilder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpn.to.dpn.list.IpAddresses;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpn.to.dpn.list.VpnInterfaces;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpn.to.dpn.list.VpnInterfacesBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.vpn.to.dpn.list.VpnInterfacesKey;
42 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 public class VpnFootprintService {
47
48     // TODO: Should this class have its own interface instead of being under IVpnManager's umbrella?
49
50     private static final Logger LOG = LoggerFactory.getLogger(VpnFootprintService.class);
51
52     private final DataBroker dataBroker;
53     private final IFibManager fibManager;
54     private final VpnOpDataSyncer vpnOpDataSyncer;
55     private final OdlInterfaceRpcService ifaceMgrRpcService;
56     private final NotificationPublishService notificationPublishService;
57
58     public VpnFootprintService(final DataBroker dataBroker,
59                                final IFibManager fibManager,
60                                final OdlInterfaceRpcService ifaceRpcService,
61                                final NotificationPublishService notificationPublishService,
62                                final VpnOpDataSyncer vpnOpDataSyncer) {
63         this.dataBroker = dataBroker;
64         this.fibManager = fibManager;
65         this.vpnOpDataSyncer = vpnOpDataSyncer;
66         this.ifaceMgrRpcService = ifaceRpcService;
67         this.notificationPublishService = notificationPublishService;
68     }
69
70     public void updateVpnToDpnMapping(BigInteger dpId, String vpnName, String interfaceName, boolean add) {
71         long vpnId = VpnUtil.getVpnId(dataBroker, vpnName);
72         if (dpId == null) {
73             dpId = InterfaceUtils.getDpnForInterface(ifaceMgrRpcService, interfaceName);
74         }
75         if(!dpId.equals(BigInteger.ZERO)) {
76             if(add) {
77                 // Considering the possibility of VpnInstanceOpData not being ready yet cause the VPN is
78                 // still in its creation process
79                 if ( vpnId == VpnConstants.INVALID_ID ) {
80                     vpnOpDataSyncer.waitForVpnDataReady(VpnOpDataSyncer.VpnOpDataType.vpnInstanceToId, vpnName,
81                                                    VpnConstants.PER_VPN_INSTANCE_OPDATA_MAX_WAIT_TIME_IN_MILLISECONDS);
82                     vpnId = VpnUtil.getVpnId(dataBroker, vpnName);
83                 }
84                 createOrUpdateVpnToDpnList(vpnId, dpId, interfaceName, vpnName);
85             } else {
86                 removeOrUpdateVpnToDpnList(vpnId, dpId, interfaceName, vpnName);
87             }
88         }
89     }
90
91     private void createOrUpdateVpnToDpnList(long vpnId, BigInteger dpnId, String intfName, String vpnName) {
92         String routeDistinguisher = VpnUtil.getVpnRdFromVpnInstanceConfig(dataBroker, vpnName);
93         String rd = (routeDistinguisher == null) ? vpnName : routeDistinguisher;
94         Boolean newDpnOnVpn = Boolean.FALSE;
95
96         synchronized (vpnName.intern()) {
97             WriteTransaction writeTxn = dataBroker.newWriteOnlyTransaction();
98             InstanceIdentifier<VpnToDpnList> id = VpnUtil.getVpnToDpnListIdentifier(rd, dpnId);
99             Optional<VpnToDpnList> dpnInVpn = VpnUtil.read(dataBroker, LogicalDatastoreType.OPERATIONAL, id);
100             VpnInterfaces vpnInterface = new VpnInterfacesBuilder().setInterfaceName(intfName).build();
101
102             if (dpnInVpn.isPresent()) {
103                 VpnToDpnList vpnToDpnList = dpnInVpn.get();
104                 List<VpnInterfaces> vpnInterfaces = vpnToDpnList.getVpnInterfaces();
105                 if (vpnInterfaces == null) {
106                     vpnInterfaces = new ArrayList<>();
107                 }
108                 vpnInterfaces.add(vpnInterface);
109                 VpnToDpnListBuilder vpnToDpnListBuilder = new VpnToDpnListBuilder(vpnToDpnList);
110                 vpnToDpnListBuilder.setDpnState(VpnToDpnList.DpnState.Active).setVpnInterfaces(vpnInterfaces);
111
112                 writeTxn.put(LogicalDatastoreType.OPERATIONAL, id, vpnToDpnListBuilder.build(), true);
113                 /* If earlier state was inactive, it is considered new DPN coming back to the
114                  * same VPN
115                  */
116                 if (vpnToDpnList.getDpnState() == VpnToDpnList.DpnState.Inactive) {
117                     newDpnOnVpn = Boolean.TRUE;
118                 }
119             } else {
120                 List<VpnInterfaces> vpnInterfaces = new ArrayList<>();
121                 vpnInterfaces.add(vpnInterface);
122                 VpnToDpnListBuilder vpnToDpnListBuilder = new VpnToDpnListBuilder().setDpnId(dpnId);
123                 vpnToDpnListBuilder.setDpnState(VpnToDpnList.DpnState.Active).setVpnInterfaces(vpnInterfaces);
124
125                 writeTxn.put(LogicalDatastoreType.OPERATIONAL, id, vpnToDpnListBuilder.build(), true);
126                 newDpnOnVpn = Boolean.TRUE;
127             }
128             CheckedFuture<Void, TransactionCommitFailedException> futures = writeTxn.submit();
129             try {
130                 futures.get();
131             } catch (InterruptedException | ExecutionException e) {
132                 LOG.error("Error adding to dpnToVpnList for vpn {} interface {} dpn {}", vpnName, intfName, dpnId, e);
133                 throw new RuntimeException(e.getMessage());
134             }
135         }
136         /*
137          * Informing the Fib only after writeTxn is submitted successfuly.
138          */
139         if (newDpnOnVpn) {
140             LOG.debug("Sending populateFib event for new dpn {} in VPN {}", dpnId, vpnName);
141             fibManager.populateFibOnNewDpn(dpnId, vpnId, rd, new DpnEnterExitVpnWorker(dpnId, vpnName, rd,
142                                                                                        true /* entered */));
143         }
144     }
145
146     private void removeOrUpdateVpnToDpnList(long vpnId, BigInteger dpnId, String intfName, String vpnName) {
147         Boolean lastDpnOnVpn = Boolean.FALSE;
148         String rd = VpnUtil.getVpnRd(dataBroker, vpnName);
149         synchronized (vpnName.intern()) {
150             InstanceIdentifier<VpnToDpnList> id = VpnUtil.getVpnToDpnListIdentifier(rd, dpnId);
151             Optional<VpnToDpnList> dpnInVpn = VpnUtil.read(dataBroker, LogicalDatastoreType.OPERATIONAL, id);
152             WriteTransaction writeTxn = dataBroker.newWriteOnlyTransaction();
153             if (dpnInVpn.isPresent()) {
154                 List<VpnInterfaces> vpnInterfaces = dpnInVpn.get().getVpnInterfaces();
155                 VpnInterfaces currVpnInterface = new VpnInterfacesBuilder().setInterfaceName(intfName).build();
156
157                 if (vpnInterfaces.remove(currVpnInterface)) {
158                     if (vpnInterfaces.isEmpty()) {
159                         List<IpAddresses> ipAddresses = dpnInVpn.get().getIpAddresses();
160                         if (ipAddresses == null || ipAddresses.isEmpty()) {
161                             VpnToDpnListBuilder dpnInVpnBuilder =
162                                 new VpnToDpnListBuilder(dpnInVpn.get()).setDpnState(VpnToDpnList.DpnState.Inactive)
163                                                                        .setVpnInterfaces(null);
164                             writeTxn.put(LogicalDatastoreType.OPERATIONAL, id, dpnInVpnBuilder.build(), true);
165                             lastDpnOnVpn = Boolean.TRUE;
166                         } else {
167                             LOG.warn("vpn interfaces are empty but ip addresses are present for the vpn {} in dpn {}",
168                                      vpnName, dpnId);
169                         }
170                     } else {
171                         writeTxn.delete(LogicalDatastoreType.OPERATIONAL, id.child(VpnInterfaces.class,
172                                                                                    new VpnInterfacesKey(intfName)));
173                     }
174                 }
175             }
176             CheckedFuture<Void, TransactionCommitFailedException> futures = writeTxn.submit();
177             try {
178                 futures.get();
179             } catch (InterruptedException | ExecutionException e) {
180                 LOG.error("Error removing from dpnToVpnList for vpn {} interface {} dpn {}",
181                           vpnName, intfName, dpnId, e);
182                 throw new RuntimeException(e.getMessage());
183             }
184         }
185         if (lastDpnOnVpn) {
186             LOG.debug("Sending cleanup event for dpn {} in VPN {}", dpnId, vpnName);
187             fibManager.cleanUpDpnForVpn(dpnId, vpnId, rd, new DpnEnterExitVpnWorker(dpnId, vpnName, rd,
188                                                                                     false /* exited */));
189         }
190     }
191
192     void publishAddNotification(final BigInteger dpnId, final String vpnName, final String rd) {
193         LOG.debug("Sending notification for add dpn {} in vpn {} event ", dpnId, vpnName);
194         AddEventData data = new AddEventDataBuilder().setVpnName(vpnName).setRd(rd).setDpnId(dpnId).build();
195         AddDpnEvent event = new AddDpnEventBuilder().setAddEventData(data).build();
196         final ListenableFuture<? extends Object> eventFuture = notificationPublishService.offerNotification(event);
197         Futures.addCallback(eventFuture, new FutureCallback<Object>() {
198             @Override
199             public void onFailure(Throwable error) {
200                 LOG.warn("Error in notifying listeners for add dpn {} in vpn {} event ", dpnId, vpnName, error);
201             }
202
203             @Override
204             public void onSuccess(Object arg) {
205                 LOG.trace("Successful in notifying listeners for add dpn {} in vpn {} event ", dpnId, vpnName);
206             }
207         });
208     }
209
210     void publishRemoveNotification(final BigInteger dpnId, final String vpnName, final String rd) {
211         LOG.debug("Sending notification for remove dpn {} in vpn {} event ", dpnId, vpnName);
212         RemoveEventData data = new RemoveEventDataBuilder().setVpnName(vpnName).setRd(rd).setDpnId(dpnId).build();
213         RemoveDpnEvent event = new RemoveDpnEventBuilder().setRemoveEventData(data).build();
214         final ListenableFuture<? extends Object> eventFuture = notificationPublishService.offerNotification(event);
215         Futures.addCallback(eventFuture, new FutureCallback<Object>() {
216             @Override
217             public void onFailure(Throwable error) {
218                 LOG.warn("Error in notifying listeners for remove dpn {} in vpn {} event ", dpnId, vpnName, error);
219             }
220
221             @Override
222             public void onSuccess(Object arg) {
223                 LOG.trace("Successful in notifying listeners for remove dpn {} in vpn {} event ", dpnId, vpnName);
224             }
225         });
226     }
227
228
229     /**
230      * JobCallback class is used as a future callback for
231      * main and rollback workers to handle success and failure.
232      */
233     private class DpnEnterExitVpnWorker implements FutureCallback<List<Void>> {
234         BigInteger dpnId;
235         String vpnName;
236         String rd;
237         boolean entered;
238
239         public DpnEnterExitVpnWorker(BigInteger dpnId, String vpnName, String rd, boolean entered) {
240             this.entered = entered;
241             this.dpnId = dpnId;
242             this.vpnName = vpnName;
243             this.rd = rd;
244         }
245
246         /**
247          * @param voids
248          * This implies that all the future instances have returned success. -- TODO: Confirm this
249          */
250         @Override
251         public void onSuccess(List<Void> voids) {
252             if (entered) {
253                 publishAddNotification(dpnId, vpnName, rd);
254             } else {
255                 publishRemoveNotification(dpnId, vpnName, rd);
256             }
257         }
258
259         /**
260          *
261          * @param throwable
262          * This method is used to handle failure callbacks.
263          * If more retry needed, the retrycount is decremented and mainworker is executed again.
264          * After retries completed, rollbackworker is executed.
265          * If rollbackworker fails, this is a double-fault. Double fault is logged and ignored.
266          */
267         @Override
268         public void onFailure(Throwable throwable) {
269             LOG.warn("Job: failed with exception: ", throwable);
270         }
271     }
272
273 }