ELAN: skip remote unicast MACs
[netvirt.git] / fibmanager / fibmanager-impl / src / main / java / org / opendaylight / netvirt / fibmanager / EvpnVrfEntryHandler.java
1 /*
2  * Copyright (c) 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.fibmanager;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.util.concurrent.ListenableFuture;
13 import java.math.BigInteger;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.List;
17 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
18 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
19 import org.opendaylight.genius.mdsalutil.ActionInfo;
20 import org.opendaylight.genius.mdsalutil.InstructionInfo;
21 import org.opendaylight.genius.mdsalutil.NwConstants;
22 import org.opendaylight.genius.mdsalutil.actions.ActionGroup;
23 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetDestination;
24 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldTunnelId;
25 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
26 import org.opendaylight.genius.utils.batching.SubTransaction;
27 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
28 import org.opendaylight.netvirt.elanmanager.api.IElanService;
29 import org.opendaylight.netvirt.fibmanager.api.RouteOrigin;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.state.Interface;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.SubnetRoute;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.fibentries.VrfTables;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.fibentries.VrfTablesKey;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntry;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3nexthop.rev150409.l3nexthop.vpnnexthops.VpnNexthop;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.prefix.to._interface.vpn.ids.Prefixes;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntry;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnToDpnList;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.to.extraroutes.vpn.extra.routes.Routes;
41 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46 public class EvpnVrfEntryHandler extends BaseVrfEntryHandler implements IVrfEntryHandler {
47     private static final Logger LOG = LoggerFactory.getLogger(EvpnVrfEntryHandler.class);
48     private final DataBroker dataBroker;
49     private final VrfEntryListener vrfEntryListener;
50     private final BgpRouteVrfEntryHandler bgpRouteVrfEntryHandler;
51     private final NexthopManager nexthopManager;
52     private final JobCoordinator jobCoordinator;
53     private final IElanService elanManager;
54
55     EvpnVrfEntryHandler(DataBroker broker, VrfEntryListener vrfEntryListener,
56             BgpRouteVrfEntryHandler bgpRouteVrfEntryHandler, NexthopManager nexthopManager,
57             JobCoordinator jobCoordinator, IElanService elanManager, FibUtil fibUtil) {
58         super(broker, nexthopManager, null, fibUtil);
59         this.dataBroker = broker;
60         this.vrfEntryListener = vrfEntryListener;
61         this.bgpRouteVrfEntryHandler = bgpRouteVrfEntryHandler;
62         this.nexthopManager = nexthopManager;
63         this.jobCoordinator = jobCoordinator;
64         this.elanManager = elanManager;
65     }
66
67     @Override
68     public void createFlows(InstanceIdentifier<VrfEntry> identifier, VrfEntry vrfEntry, String rd) {
69         LOG.info("Initiating creation of Evpn Flows");
70         final VrfTablesKey vrfTableKey = identifier.firstKeyOf(VrfTables.class);
71         final VpnInstanceOpDataEntry vpnInstance = getFibUtil().getVpnInstanceOpData(
72                 vrfTableKey.getRouteDistinguisher()).get();
73         Long vpnId = vpnInstance.getVpnId();
74         Preconditions.checkNotNull(vpnInstance, "Vpn Instance not available " + vrfTableKey.getRouteDistinguisher());
75         Preconditions.checkNotNull(vpnId, "Vpn Instance with rd " + vpnInstance.getVrfId()
76                 + " has null vpnId!");
77         if (RouteOrigin.value(vrfEntry.getOrigin()) == RouteOrigin.CONNECTED) {
78             SubnetRoute subnetRoute = vrfEntry.getAugmentation(SubnetRoute.class);
79             final List<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
80             final long elanTag = subnetRoute.getElantag();
81             LOG.trace("SubnetRoute augmented vrfentry found for rd {} prefix {} with elantag {}",
82                     rd, vrfEntry.getDestPrefix(), elanTag);
83             if (vpnToDpnList != null) {
84                 jobCoordinator.enqueueJob("FIB-" + rd + "-" + vrfEntry.getDestPrefix(),
85                     () -> {
86                         WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
87                         for (final VpnToDpnList curDpn : vpnToDpnList) {
88                             if (curDpn.getDpnState() == VpnToDpnList.DpnState.Active) {
89                                 vrfEntryListener.installSubnetRouteInFib(curDpn.getDpnId(), elanTag, rd,
90                                         vpnId, vrfEntry, tx);
91                             }
92                         }
93                         List<ListenableFuture<Void>> futures = new ArrayList<>();
94                         futures.add(tx.submit());
95                         return futures;
96                     });
97             }
98             return;
99         }
100         Prefixes localNextHopInfo = getFibUtil().getPrefixToInterface(vpnInstance.getVpnId(), vrfEntry.getDestPrefix());
101         List<BigInteger> localDpnId = new ArrayList<>();
102         boolean isNatPrefix = false;
103         if (Prefixes.PrefixCue.Nat.equals(localNextHopInfo.getPrefixCue())) {
104             LOG.info("NAT Prefix {} with vpnId {} rd {}. Skip local dpn {} FIB processing",
105                     vrfEntry.getDestPrefix(), vpnId, rd, localNextHopInfo.getDpnId());
106             localDpnId.add(localNextHopInfo.getDpnId());
107             isNatPrefix = true;
108         } else {
109             localDpnId = createLocalEvpnFlows(vpnInstance.getVpnId(), rd, vrfEntry,
110                     localNextHopInfo);
111         }
112         createRemoteEvpnFlows(rd, vrfEntry, vpnInstance, localDpnId, vrfTableKey, isNatPrefix);
113     }
114
115     @Override
116     public void removeFlows(InstanceIdentifier<VrfEntry> identifier, VrfEntry vrfEntry, String rd) {
117         final VrfTablesKey vrfTableKey = identifier.firstKeyOf(VrfTables.class);
118         final VpnInstanceOpDataEntry vpnInstance = getFibUtil().getVpnInstanceOpData(
119                 vrfTableKey.getRouteDistinguisher()).get();
120         if (vpnInstance == null) {
121             LOG.error("VPN Instance for rd {} is not available from VPN Op Instance Datastore", rd);
122             return;
123         }
124         VpnNexthop localNextHopInfo = nexthopManager.getVpnNexthop(vpnInstance.getVpnId(),
125                 vrfEntry.getDestPrefix());
126         List<BigInteger> localDpnId = checkDeleteLocalEvpnFLows(vpnInstance.getVpnId(), rd, vrfEntry, localNextHopInfo);
127         deleteRemoteEvpnFlows(rd, vrfEntry, vpnInstance, vrfTableKey, localDpnId);
128         vrfEntryListener.cleanUpOpDataForFib(vpnInstance.getVpnId(), rd, vrfEntry);
129     }
130
131     @Override
132     public void updateFlows(InstanceIdentifier<VrfEntry> identifier, VrfEntry original, VrfEntry update, String rd) {
133         //Not used
134     }
135
136     private List<BigInteger> createLocalEvpnFlows(long vpnId, String rd, VrfEntry vrfEntry,
137                                                   Prefixes localNextHopInfo) {
138         List<BigInteger> returnLocalDpnId = new ArrayList<>();
139         String localNextHopIP = vrfEntry.getDestPrefix();
140         if (localNextHopInfo == null) {
141             //Handle extra routes and imported routes
142             Routes extraRoute = getVpnToExtraroute(vpnId, rd, vrfEntry.getDestPrefix());
143             if (extraRoute != null) {
144                 for (String nextHopIp : extraRoute.getNexthopIpList()) {
145                     LOG.info("NextHop IP for destination {} is {}", vrfEntry.getDestPrefix(), nextHopIp);
146                     if (nextHopIp != null) {
147                         localNextHopInfo = getFibUtil().getPrefixToInterface(vpnId, nextHopIp + "/32");
148                         if (localNextHopInfo != null) {
149                             localNextHopIP = nextHopIp + "/32";
150                             BigInteger dpnId = checkCreateLocalEvpnFlows(localNextHopInfo, localNextHopIP, vpnId,
151                                     rd, vrfEntry);
152                             returnLocalDpnId.add(dpnId);
153                         }
154                     }
155                 }
156             }
157         } else {
158             LOG.info("Creating local EVPN flows for prefix {} rd {} route-paths {} evi {}.",
159                     vrfEntry.getDestPrefix(), rd, vrfEntry.getRoutePaths(), vrfEntry.getL3vni());
160             BigInteger dpnId = checkCreateLocalEvpnFlows(localNextHopInfo, localNextHopIP, vpnId,
161                     rd, vrfEntry);
162             returnLocalDpnId.add(dpnId);
163         }
164         return returnLocalDpnId;
165     }
166
167     private BigInteger checkCreateLocalEvpnFlows(Prefixes localNextHopInfo, String localNextHopIP,
168                                                  final Long vpnId, final String rd,
169                                                  final VrfEntry vrfEntry) {
170         final BigInteger dpnId = localNextHopInfo.getDpnId();
171         String jobKey = "FIB-" + vpnId.toString() + "-" + dpnId.toString() + "-" + vrfEntry.getDestPrefix();
172         final long groupId = nexthopManager.createLocalNextHop(vpnId, dpnId,
173             localNextHopInfo.getVpnInterfaceName(), localNextHopIP, vrfEntry.getDestPrefix(),
174             vrfEntry.getGatewayMacAddress(), jobKey);
175         LOG.debug("LocalNextHopGroup {} created/reused for prefix {} rd {} evi {} route-paths {}", groupId,
176             vrfEntry.getDestPrefix(), rd, vrfEntry.getL3vni(), vrfEntry.getRoutePaths());
177
178         final List<InstructionInfo> instructions = Collections.singletonList(
179             new InstructionApplyActions(
180                 Collections.singletonList(new ActionGroup(groupId))));
181         jobCoordinator.enqueueJob("FIB-" + vpnId.toString() + "-" + dpnId.toString()
182                 + "-" + vrfEntry.getDestPrefix(),
183             () -> {
184                 WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
185                 makeConnectedRoute(dpnId, vpnId, vrfEntry, rd, instructions, NwConstants.ADD_FLOW, tx, null);
186                 List<ListenableFuture<Void>> futures = new ArrayList<>();
187                 futures.add(tx.submit());
188                 return futures;
189             });
190         return dpnId;
191     }
192
193     private void createRemoteEvpnFlows(String rd, VrfEntry vrfEntry, VpnInstanceOpDataEntry vpnInstance,
194                                        List<BigInteger> localDpnId, VrfTablesKey vrfTableKey, boolean isNatPrefix) {
195         LOG.info("Creating remote EVPN flows for prefix {} rd {} route-paths {} evi {}",
196             vrfEntry.getDestPrefix(), rd, vrfEntry.getRoutePaths(), vrfEntry.getL3vni());
197         List<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
198         if (vpnToDpnList != null) {
199             jobCoordinator.enqueueJob("FIB" + rd + vrfEntry.getDestPrefix(),
200                 () -> {
201                     WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
202                     List<ListenableFuture<Void>> futures = new ArrayList<>();
203                     for (VpnToDpnList vpnDpn : vpnToDpnList) {
204                         if (!localDpnId.contains(vpnDpn.getDpnId())) {
205                             if (vpnDpn.getDpnState() == VpnToDpnList.DpnState.Active) {
206                                 createRemoteFibEntry(vpnDpn.getDpnId(), vpnInstance.getVpnId(),
207                                     vrfTableKey, vrfEntry, isNatPrefix, tx);
208                             }
209                         }
210                     }
211                     futures.add(tx.submit());
212                     return futures;
213                 });
214         }
215     }
216
217     private void createRemoteFibEntry(final BigInteger remoteDpnId, final long vpnId, final VrfTablesKey vrfTableKey,
218                                       final VrfEntry vrfEntry, boolean isNatPrefix, WriteTransaction tx) {
219
220         String rd = vrfTableKey.getRouteDistinguisher();
221         List<SubTransaction> subTxns =  new ArrayList<>();
222         LOG.debug("createremotefibentry: adding route {} for rd {} with transaction {}",
223                 vrfEntry.getDestPrefix(), rd, tx);
224         List<NexthopManager.AdjacencyResult> tunnelInterfaceList = resolveAdjacency(remoteDpnId, vpnId, vrfEntry, rd);
225
226         if (tunnelInterfaceList.isEmpty()) {
227             LOG.error("Could not get interface for route-paths: {} in vpn {}",
228                     vrfEntry.getRoutePaths(), rd);
229             LOG.warn("Failed to add Route: {} in vpn: {}",
230                     vrfEntry.getDestPrefix(), rd);
231             return;
232         }
233
234         for (NexthopManager.AdjacencyResult adjacencyResult : tunnelInterfaceList) {
235             List<ActionInfo> actionInfos = new ArrayList<>();
236             BigInteger tunnelId;
237             String prefix = adjacencyResult.getPrefix();
238             Prefixes prefixInfo = getFibUtil().getPrefixToInterface(vpnId, prefix);
239             String interfaceName = prefixInfo.getVpnInterfaceName();
240             if (vrfEntry.getOrigin().equals(RouteOrigin.BGP.getValue()) || isNatPrefix) {
241                 tunnelId = BigInteger.valueOf(vrfEntry.getL3vni());
242             } else if (elanManager.isOpenStackVniSemanticsEnforced()) {
243                 tunnelId = BigInteger.valueOf(getFibUtil().getVniForVxlanNetwork(prefixInfo.getSubnetId()).get());
244             } else {
245                 Interface interfaceState = getFibUtil().getInterfaceStateFromOperDS(interfaceName);
246                 tunnelId = BigInteger.valueOf(interfaceState.getIfIndex());
247             }
248             LOG.debug("adding set tunnel id action for label {}", tunnelId);
249             String macAddress = null;
250             String vpnName = getFibUtil().getVpnNameFromId(vpnId);
251             if (vpnName == null) {
252                 LOG.debug("Failed to get VPN name for vpnId {}", vpnId);
253                 return;
254             }
255             if (interfaceName != null) {
256                 macAddress = getFibUtil().getMacAddressFromPrefix(interfaceName, vpnName, prefix);
257                 actionInfos.add(new ActionSetFieldEthernetDestination(new MacAddress(macAddress)));
258             }
259             actionInfos.add(new ActionSetFieldTunnelId(tunnelId));
260             List<ActionInfo> egressActions =
261                     nexthopManager.getEgressActionsForInterface(adjacencyResult.getInterfaceName(), actionInfos.size());
262             if (egressActions.isEmpty()) {
263                 LOG.error("Failed to retrieve egress action for prefix {} route-paths {} interface {}."
264                         + " Aborting remote FIB entry creation..", vrfEntry.getDestPrefix(),
265                         vrfEntry.getRoutePaths(), adjacencyResult.getInterfaceName());
266                 return;
267             }
268             actionInfos.addAll(egressActions);
269             List<InstructionInfo> instructions = new ArrayList<>();
270             instructions.add(new InstructionApplyActions(actionInfos));
271             makeConnectedRoute(remoteDpnId, vpnId, vrfEntry, rd, instructions, NwConstants.ADD_FLOW, tx, subTxns);
272         }
273         LOG.debug("Successfully added FIB entry for prefix {} in rd {}", vrfEntry.getDestPrefix(), rd);
274     }
275
276     private void deleteRemoteEvpnFlows(String rd, VrfEntry vrfEntry, VpnInstanceOpDataEntry vpnInstance,
277                                        VrfTablesKey vrfTableKey, List<BigInteger> localDpnIdList) {
278         List<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
279         List<SubTransaction> subTxns =  new ArrayList<>();
280         if (vpnToDpnList != null) {
281             jobCoordinator.enqueueJob("FIB" + rd + vrfEntry.getDestPrefix(),
282                 () -> {
283                     WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
284                     final Optional<Routes> extraRouteOptional = Optional.absent();
285                     if (localDpnIdList.size() <= 0) {
286                         for (VpnToDpnList curDpn1 : vpnToDpnList) {
287                             if (RouteOrigin.value(vrfEntry.getOrigin()) == RouteOrigin.BGP) {
288                                 if (curDpn1.getDpnState() == VpnToDpnList.DpnState.Active) {
289                                     bgpRouteVrfEntryHandler.deleteRemoteRoute(BigInteger.ZERO, curDpn1.getDpnId(),
290                                         vpnInstance.getVpnId(), vrfTableKey, vrfEntry,
291                                         extraRouteOptional, tx, subTxns);
292                                 }
293                             } else {
294                                 deleteRemoteRoute(BigInteger.ZERO, curDpn1.getDpnId(),
295                                     vpnInstance.getVpnId(), vrfTableKey, vrfEntry,
296                                     extraRouteOptional, tx);
297                             }
298                         }
299                     } else {
300                         for (BigInteger localDpnId : localDpnIdList) {
301                             for (VpnToDpnList curDpn2 : vpnToDpnList) {
302                                 if (!curDpn2.getDpnId().equals(localDpnId)) {
303                                     if (RouteOrigin.value(vrfEntry.getOrigin()) == RouteOrigin.BGP) {
304                                         if (curDpn2.getDpnState() == VpnToDpnList.DpnState.Active) {
305                                             bgpRouteVrfEntryHandler.deleteRemoteRoute(localDpnId, curDpn2.getDpnId(),
306                                                 vpnInstance.getVpnId(), vrfTableKey, vrfEntry,
307                                                 extraRouteOptional, tx, subTxns);
308                                         }
309                                     } else {
310                                         deleteRemoteRoute(localDpnId, curDpn2.getDpnId(),
311                                             vpnInstance.getVpnId(), vrfTableKey, vrfEntry,
312                                             extraRouteOptional, tx);
313                                     }
314                                 }
315                             }
316                         }
317                     }
318                     List<ListenableFuture<Void>> futures = new ArrayList<>();
319                     futures.add(tx.submit());
320                     return futures;
321                 });
322         }
323     }
324
325     private List<BigInteger> checkDeleteLocalEvpnFLows(long vpnId, String rd, VrfEntry vrfEntry,
326                                                        VpnNexthop localNextHopInfo) {
327         List<BigInteger> returnLocalDpnId = new ArrayList<>();
328         if (localNextHopInfo == null) {
329             //Handle extra routes and imported routes
330         } else {
331             final BigInteger dpnId = localNextHopInfo.getDpnId();
332             jobCoordinator.enqueueJob("FIB-" + rd + "-" + vrfEntry.getDestPrefix(),
333                 () -> {
334                     WriteTransaction tx = dataBroker.newWriteOnlyTransaction();
335                     makeConnectedRoute(dpnId, vpnId, vrfEntry, rd, null, NwConstants.DEL_FLOW, tx, null);
336                     List<ListenableFuture<Void>> futures = new ArrayList<>();
337                     futures.add(tx.submit());
338                     return futures;
339                 });
340             //TODO: verify below adjacency call need to be optimized (?)
341             deleteLocalAdjacency(dpnId, vpnId, vrfEntry.getDestPrefix(), vrfEntry.getDestPrefix());
342             returnLocalDpnId.add(dpnId);
343         }
344         return returnLocalDpnId;
345     }
346 }