Handle nullable lists in fibmanager
[netvirt.git] / fibmanager / impl / src / main / java / org / opendaylight / netvirt / fibmanager / BgpRouteVrfEntryHandler.java
1 /*
2  * Copyright © 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 static java.util.stream.Collectors.toList;
11 import static org.opendaylight.netvirt.fibmanager.FibUtil.nullToEmpty;
12
13 import com.google.common.base.Optional;
14 import java.math.BigInteger;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.concurrent.BlockingQueue;
19 import java.util.concurrent.LinkedBlockingQueue;
20 import java.util.function.Consumer;
21 import javax.annotation.Nullable;
22 import javax.annotation.PostConstruct;
23 import javax.inject.Inject;
24 import javax.inject.Singleton;
25 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
26 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.genius.datastoreutils.listeners.DataTreeEventCallbackRegistrar;
29 import org.opendaylight.genius.infra.ManagedNewTransactionRunner;
30 import org.opendaylight.genius.infra.ManagedNewTransactionRunnerImpl;
31 import org.opendaylight.genius.mdsalutil.ActionInfo;
32 import org.opendaylight.genius.mdsalutil.InstructionInfo;
33 import org.opendaylight.genius.mdsalutil.NwConstants;
34 import org.opendaylight.genius.mdsalutil.actions.ActionGroup;
35 import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadInPort;
36 import org.opendaylight.genius.mdsalutil.actions.ActionPushMpls;
37 import org.opendaylight.genius.mdsalutil.actions.ActionRegLoad;
38 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetDestination;
39 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldMplsLabel;
40 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldTunnelId;
41 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
42 import org.opendaylight.genius.utils.batching.ActionableResource;
43 import org.opendaylight.genius.utils.batching.ActionableResourceImpl;
44 import org.opendaylight.genius.utils.batching.ResourceBatchingManager;
45 import org.opendaylight.genius.utils.batching.ResourceHandler;
46 import org.opendaylight.genius.utils.batching.SubTransaction;
47 import org.opendaylight.infrautils.utils.concurrent.ListenableFutures;
48 import org.opendaylight.netvirt.vpnmanager.api.VpnExtraRouteHelper;
49 import org.opendaylight.serviceutils.upgrade.UpgradeState;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeBase;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeMplsOverGre;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeVxlan;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.fibentries.VrfTables;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.fibentries.VrfTablesKey;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.fibmanager.rev150330.vrfentries.VrfEntry;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3nexthop.rev150409.l3nexthop.vpnnexthops.VpnNexthop;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.prefix.to._interface.vpn.ids.Prefixes;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntry;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnToDpnList;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.to.extraroutes.vpn.extra.routes.Routes;
62 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66
67 @Singleton
68 public class BgpRouteVrfEntryHandler extends BaseVrfEntryHandler
69         implements ResourceHandler, IVrfEntryHandler {
70
71     private static final Logger LOG = LoggerFactory.getLogger(BgpRouteVrfEntryHandler.class);
72     private static final int BATCH_INTERVAL = 500;
73     private static final int BATCH_SIZE = 1000;
74
75     private final DataBroker dataBroker;
76     private final ManagedNewTransactionRunner txRunner;
77     private final BlockingQueue<ActionableResource> vrfEntryBufferQ = new LinkedBlockingQueue<>();
78     private final ResourceBatchingManager resourceBatchingManager;
79     private final NexthopManager nexthopManager;
80
81     @Inject
82     public BgpRouteVrfEntryHandler(final DataBroker dataBroker,
83                                    final NexthopManager nexthopManager,
84                                    final FibUtil fibUtil,
85                                    final UpgradeState upgradeState,
86                                    final DataTreeEventCallbackRegistrar eventCallbacks) {
87         super(dataBroker, nexthopManager, null, fibUtil, upgradeState, eventCallbacks);
88         this.dataBroker = dataBroker;
89         this.txRunner = new ManagedNewTransactionRunnerImpl(dataBroker);
90         this.nexthopManager = nexthopManager;
91
92         resourceBatchingManager = ResourceBatchingManager.getInstance();
93         resourceBatchingManager.registerBatchableResource("FIB-VRFENTRY", vrfEntryBufferQ, this);
94     }
95
96     @PostConstruct
97     public void init() {
98         LOG.info("{} start", getClass().getSimpleName());
99     }
100
101     @Override
102     public void close() {
103         LOG.info("{} close", getClass().getSimpleName());
104     }
105
106     @Override
107     public DataBroker getResourceBroker() {
108         return dataBroker;
109     }
110
111     @Override
112     public int getBatchSize() {
113         return BATCH_SIZE;
114     }
115
116     @Override
117     public int getBatchInterval() {
118         return BATCH_INTERVAL;
119     }
120
121     @Override
122     public LogicalDatastoreType getDatastoreType() {
123         return LogicalDatastoreType.CONFIGURATION;
124     }
125
126     @Override
127     public void update(WriteTransaction tx, LogicalDatastoreType datastoreType, InstanceIdentifier identifier,
128                        Object original, Object update, List<SubTransaction> subTxns) {
129         if (original instanceof VrfEntry && update instanceof VrfEntry) {
130             createFibEntries(tx, identifier, (VrfEntry) update, subTxns);
131         }
132     }
133
134     @Override
135     public void create(WriteTransaction tx, LogicalDatastoreType datastoreType, InstanceIdentifier identifier,
136                        Object vrfEntry, List<SubTransaction> subTxns) {
137         if (vrfEntry instanceof VrfEntry) {
138             createFibEntries(tx, identifier, (VrfEntry) vrfEntry, subTxns);
139         }
140     }
141
142     @Override
143     public void delete(WriteTransaction tx, LogicalDatastoreType datastoreType, InstanceIdentifier identifier,
144                        Object vrfEntry, List<SubTransaction> subTxns) {
145         if (vrfEntry instanceof VrfEntry) {
146             deleteFibEntries(tx, identifier, (VrfEntry) vrfEntry, subTxns);
147         }
148     }
149
150     @Override
151     public void createFlows(InstanceIdentifier<VrfEntry> identifier, VrfEntry vrfEntry, String rd) {
152         ActionableResource actResource = new ActionableResourceImpl(rd + vrfEntry.getDestPrefix());
153         actResource.setAction(ActionableResource.CREATE);
154         actResource.setInstanceIdentifier(identifier);
155         actResource.setInstance(vrfEntry);
156         vrfEntryBufferQ.add(actResource);
157     }
158
159     @Override
160     public void removeFlows(InstanceIdentifier<VrfEntry> identifier, VrfEntry vrfEntry, String rd) {
161         ActionableResource actResource = new ActionableResourceImpl(rd + vrfEntry.getDestPrefix());
162         actResource.setAction(ActionableResource.DELETE);
163         actResource.setInstanceIdentifier(identifier);
164         actResource.setInstance(vrfEntry);
165         vrfEntryBufferQ.add(actResource);
166     }
167
168     @Override
169     public void updateFlows(InstanceIdentifier<VrfEntry> identifier, VrfEntry original, VrfEntry update, String rd) {
170         ActionableResource actResource = new ActionableResourceImpl(rd + update.getDestPrefix());
171         actResource.setAction(ActionableResource.UPDATE);
172         actResource.setInstanceIdentifier(identifier);
173         actResource.setInstance(update);
174         actResource.setOldInstance(original);
175         vrfEntryBufferQ.add(actResource);
176     }
177
178     /*
179       Please note that the following createFibEntries will be invoked only for BGP Imported Routes.
180       The invocation of the following method is via create() callback from the MDSAL Batching Infrastructure
181       provided by ResourceBatchingManager
182      */
183     private void createFibEntries(WriteTransaction writeTx, final InstanceIdentifier<VrfEntry> vrfEntryIid,
184                                   final VrfEntry vrfEntry, List<SubTransaction> subTxns) {
185         final VrfTablesKey vrfTableKey = vrfEntryIid.firstKeyOf(VrfTables.class);
186         LOG.trace("Creating fib entry for vrfEntry with destPrefix{}, rd {}",
187             vrfEntry.getDestPrefix(), vrfTableKey.getRouteDistinguisher());
188         final VpnInstanceOpDataEntry vpnInstance =
189                 getFibUtil().getVpnInstance(vrfTableKey.getRouteDistinguisher());
190         if (vpnInstance == null || vpnInstance.getVpnId() == null) {
191             LOG.error("Vpn Instance not availabe {}", vrfTableKey.getRouteDistinguisher());
192             return;
193         }
194         final Collection<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
195         if (vpnToDpnList != null) {
196             for (VpnToDpnList vpnDpn : vpnToDpnList) {
197                 LOG.trace("Dpnstate is {} for dpn {} in vpn {}", vpnDpn.getDpnState(), vpnDpn.getDpnId(),
198                     vpnInstance.getVpnId());
199                 if (vpnDpn.getDpnState() == VpnToDpnList.DpnState.Active) {
200                     createRemoteFibEntry(vpnDpn.getDpnId(), vpnInstance.getVpnId(), vrfTableKey.getRouteDistinguisher(),
201                             vrfEntry, writeTx, subTxns);
202                 }
203             }
204         }
205         LOG.trace("Created fib entry for vrfEntry with destPrefix{}, rd {}",
206             vrfEntry.getDestPrefix(), vrfTableKey.getRouteDistinguisher());
207     }
208
209     /*
210       Please note that the following deleteFibEntries will be invoked only for BGP Imported Routes.
211       The invocation of the following method is via delete() callback from the MDSAL Batching Infrastructure
212       provided by ResourceBatchingManager
213      */
214     private void deleteFibEntries(WriteTransaction writeTx, final InstanceIdentifier<VrfEntry> identifier,
215                                   final VrfEntry vrfEntry, List<SubTransaction> subTxns) {
216         final VrfTablesKey vrfTableKey = identifier.firstKeyOf(VrfTables.class);
217         String rd = vrfTableKey.getRouteDistinguisher();
218         final VpnInstanceOpDataEntry vpnInstance =
219                 getFibUtil().getVpnInstance(vrfTableKey.getRouteDistinguisher());
220         if (vpnInstance == null) {
221             LOG.debug("VPN Instance for rd {} is not available from VPN Op Instance Datastore", rd);
222             return;
223         }
224         String vpnName = getFibUtil().getVpnNameFromId(vpnInstance.getVpnId());
225         final Collection<VpnToDpnList> vpnToDpnList = vpnInstance.getVpnToDpnList();
226         if (vpnToDpnList != null) {
227             List<String> usedRds = VpnExtraRouteHelper.getUsedRds(dataBroker,
228                     vpnInstance.getVpnId(), vrfEntry.getDestPrefix());
229             Optional<Routes> extraRouteOptional;
230             //Is this fib route an extra route? If yes, get the nexthop which would be an adjacency in the vpn
231             if (usedRds != null && !usedRds.isEmpty()) {
232                 if (usedRds.size() > 1) {
233                     LOG.error("The extra route prefix is still present in some DPNs");
234                     return ;
235                 } else {
236                     extraRouteOptional = VpnExtraRouteHelper.getVpnExtraroutes(dataBroker, vpnName,
237                             usedRds.get(0), vrfEntry.getDestPrefix());
238                 }
239             } else {
240                 extraRouteOptional = Optional.absent();
241             }
242             for (VpnToDpnList curDpn : vpnToDpnList) {
243                 if (curDpn.getDpnState() == VpnToDpnList.DpnState.Active) {
244                     deleteRemoteRoute(BigInteger.ZERO, curDpn.getDpnId(), vpnInstance.getVpnId(),
245                             vrfTableKey, vrfEntry, extraRouteOptional, writeTx, subTxns);
246                 }
247             }
248         }
249     }
250
251     public void programRemoteFibForBgpRoutes(final BigInteger remoteDpnId,
252                                              final long vpnId,
253                                              final VrfEntry vrfEntry,
254                                              WriteTransaction tx,
255                                              String rd,
256                                              List<NexthopManager.AdjacencyResult> adjacencyResults,
257                                              List<SubTransaction> subTxns) {
258         if (nullToEmpty(vrfEntry.getRoutePaths()).size() > 2) {
259             LOG.error("DC-GW can advertise only 2 bestPaths for prefix {}", vrfEntry.getDestPrefix());
260             return;
261         }
262         LOG.trace("Start programming remote fib for destPrefix {}, vpnId {}, dpnId {}",
263             vrfEntry.getDestPrefix(), vpnId, remoteDpnId);
264         if (adjacencyResults.size() == 1) {
265             programRemoteFib(remoteDpnId, vpnId, vrfEntry, tx, rd, adjacencyResults, subTxns);
266             return;
267         }
268         // ECMP Use case, point to LB group. Move the mpls label accordingly.
269         List<String> tunnelList =
270                 adjacencyResults.stream()
271                         .map(NexthopManager.AdjacencyResult::getNextHopIp)
272                         .sorted().collect(toList());
273         String lbGroupKey = FibUtil.getGreLbGroupKey(tunnelList);
274         long groupId = nexthopManager.createNextHopPointer(lbGroupKey);
275         int index = 0;
276         List<ActionInfo> actionInfos = new ArrayList<>();
277         for (NexthopManager.AdjacencyResult adjResult : adjacencyResults) {
278             String nextHopIp = adjResult.getNextHopIp();
279             java.util.Optional<Long> optionalLabel = FibUtil.getLabelForNextHop(vrfEntry, nextHopIp);
280             if (!optionalLabel.isPresent()) {
281                 LOG.warn("NextHopIp {} not found in vrfEntry {}", nextHopIp, vrfEntry);
282                 continue;
283             }
284             long label = optionalLabel.get();
285
286             actionInfos.add(new ActionRegLoad(index, FibConstants.NXM_REG_MAPPING.get(index++), 0,
287                     31, label));
288         }
289         List<InstructionInfo> instructions = new ArrayList<>();
290         actionInfos.add(new ActionGroup(index, groupId));
291         instructions.add(new InstructionApplyActions(actionInfos));
292         makeConnectedRoute(remoteDpnId, vpnId, vrfEntry, rd, instructions, NwConstants.ADD_FLOW, tx, subTxns);
293         LOG.trace("End programming remote fib for destPrefix {}, vpnId {}, dpnId {}",
294                 vrfEntry.getDestPrefix(), vpnId, remoteDpnId);
295     }
296
297     public void createRemoteFibEntry(final BigInteger remoteDpnId,
298                                      final long vpnId,
299                                      final String rd,
300                                      final VrfEntry vrfEntry,
301                                      WriteTransaction tx,
302                                      List<SubTransaction> subTxns) {
303         if (tx == null) {
304             ListenableFutures.addErrorLogging(txRunner.callWithNewWriteOnlyTransactionAndSubmit(
305                 newTx -> createRemoteFibEntry(remoteDpnId, vpnId, rd, vrfEntry, newTx, subTxns)), LOG,
306                 "Error creating remote FIB entry");
307             return;
308         }
309
310         LOG.debug("createRemoteFibEntry: adding route {} for rd {} on remoteDpnId {}",
311                 vrfEntry.getDestPrefix(), rd, remoteDpnId);
312
313         List<NexthopManager.AdjacencyResult> adjacencyResults =
314                 resolveAdjacency(remoteDpnId, vpnId, vrfEntry, rd);
315         if (adjacencyResults.isEmpty()) {
316             LOG.error("Could not get interface for route-paths: {} in vpn {}", vrfEntry.getRoutePaths(), rd);
317             LOG.warn("Failed to add Route: {} in vpn: {}", vrfEntry.getDestPrefix(), rd);
318             return;
319         }
320
321         programRemoteFibForBgpRoutes(remoteDpnId, vpnId, vrfEntry, tx, rd, adjacencyResults, subTxns);
322
323         LOG.debug("Successfully added FIB entry for prefix {} in vpnId {}", vrfEntry.getDestPrefix(), vpnId);
324     }
325
326     private void deleteFibEntryForBgpRoutes(BigInteger remoteDpnId, long vpnId, VrfEntry vrfEntry,
327                                              String rd, WriteTransaction tx, List<SubTransaction> subTxns) {
328         // When the tunnel is removed the fib entries should be reprogrammed/deleted depending on
329         // the adjacencyResults.
330         List<NexthopManager.AdjacencyResult> adjacencyResults = resolveAdjacency(remoteDpnId, vpnId, vrfEntry, rd);
331         if (!adjacencyResults.isEmpty()) {
332             programRemoteFibForBgpRoutes(remoteDpnId, vpnId, vrfEntry, tx, rd, adjacencyResults, subTxns);
333         }
334     }
335
336     public void deleteRemoteRoute(@Nullable final BigInteger localDpnId, final BigInteger remoteDpnId,
337                                   final long vpnId, final VrfTablesKey vrfTableKey,
338                                   final VrfEntry vrfEntry, Optional<Routes> extraRouteOptional,
339                                   @Nullable WriteTransaction tx, List<SubTransaction> subTxns) {
340         if (tx == null) {
341             ListenableFutures.addErrorLogging(txRunner.callWithNewWriteOnlyTransactionAndSubmit(
342                 newTx -> deleteRemoteRoute(localDpnId, remoteDpnId, vpnId, vrfTableKey, vrfEntry,
343                         extraRouteOptional, newTx)), LOG, "Error deleting remote route");
344             return;
345         }
346
347         LOG.debug("deleting remote route: prefix={}, vpnId={} localDpnId {} remoteDpnId {}",
348                 vrfEntry.getDestPrefix(), vpnId, localDpnId, remoteDpnId);
349         String rd = vrfTableKey.getRouteDistinguisher();
350
351         if (localDpnId != null && localDpnId != BigInteger.ZERO) {
352             // localDpnId is not known when clean up happens for last vm for a vpn on a dpn
353             if (extraRouteOptional.isPresent()) {
354                 nexthopManager.deleteLoadBalancingNextHop(vpnId, remoteDpnId, vrfEntry.getDestPrefix());
355             }
356             deleteFibEntryForBgpRoutes(remoteDpnId, vpnId, vrfEntry, rd, tx, subTxns);
357             return;
358         }
359
360         // below two reads are kept as is, until best way is found to identify dpnID
361         VpnNexthop localNextHopInfo = nexthopManager.getVpnNexthop(vpnId, vrfEntry.getDestPrefix());
362         if (extraRouteOptional.isPresent()) {
363             nexthopManager.deleteLoadBalancingNextHop(vpnId, remoteDpnId, vrfEntry.getDestPrefix());
364         } else {
365             checkDpnDeleteFibEntry(localNextHopInfo, remoteDpnId, vpnId, vrfEntry, rd, tx, subTxns);
366         }
367     }
368
369     public Consumer<? super VrfEntry> getConsumerForCreatingRemoteFib(
370             final BigInteger dpnId, final long vpnId, final String rd,
371             final String remoteNextHopIp, final Optional<VrfTables> vrfTable,
372             WriteTransaction writeCfgTxn, List<SubTransaction> subTxns) {
373         return vrfEntry -> nullToEmpty(vrfEntry.getRoutePaths()).stream()
374                 .filter(routes -> !routes.getNexthopAddress().isEmpty()
375                         && remoteNextHopIp.trim().equals(routes.getNexthopAddress().trim()))
376                 .findFirst()
377                 .ifPresent(routes -> {
378                     LOG.trace("creating remote FIB entry for prefix {} rd {} on Dpn {}",
379                             vrfEntry.getDestPrefix(), rd, dpnId);
380                     createRemoteFibEntry(dpnId, vpnId, vrfTable.get().getRouteDistinguisher(),
381                             vrfEntry, writeCfgTxn, subTxns);
382                 });
383     }
384
385     public Consumer<? super VrfEntry> getConsumerForDeletingRemoteFib(
386             final BigInteger dpnId, final long vpnId,
387             final String remoteNextHopIp, final Optional<VrfTables> vrfTable,
388             WriteTransaction writeCfgTxn, List<SubTransaction> subTxns) {
389         return vrfEntry -> nullToEmpty(vrfEntry.getRoutePaths()).stream()
390                 .filter(routes -> !routes.getNexthopAddress().isEmpty()
391                         && remoteNextHopIp.trim().equals(routes.getNexthopAddress().trim()))
392                 .findFirst()
393                 .ifPresent(routes -> {
394                     LOG.trace(" deleting remote FIB entry {}", vrfEntry);
395                     deleteRemoteRoute(null, dpnId, vpnId, vrfTable.get().key(), vrfEntry,
396                             Optional.absent(), writeCfgTxn, subTxns);
397                 });
398     }
399
400     @Override
401     protected void addTunnelInterfaceActions(NexthopManager.AdjacencyResult adjacencyResult, long vpnId,
402             VrfEntry vrfEntry, List<ActionInfo> actionInfos, String rd) {
403         Class<? extends TunnelTypeBase> tunnelType = VpnExtraRouteHelper
404                 .getTunnelType(getNextHopManager().getItmManager(), adjacencyResult.getInterfaceName());
405         if (tunnelType == null) {
406             LOG.debug("Tunnel type not found for vrfEntry {}", vrfEntry);
407             return;
408         }
409         String nextHopIp = adjacencyResult.getNextHopIp();
410         if (tunnelType.equals(TunnelTypeMplsOverGre.class)) {
411             java.util.Optional<Long> optionalLabel = FibUtil.getLabelForNextHop(vrfEntry, nextHopIp);
412             if (!optionalLabel.isPresent()) {
413                 LOG.warn("NextHopIp {} not found in vrfEntry {}", nextHopIp, vrfEntry);
414                 return;
415             }
416             long label = optionalLabel.get();
417             LOG.debug("addTunnelInterfaceActions: Push label action for prefix {} rd {} l3vni {} nextHop {}",
418                     vrfEntry.getDestPrefix(), rd, vrfEntry.getL3vni(), nextHopIp);
419             actionInfos.add(new ActionPushMpls());
420             actionInfos.add(new ActionSetFieldMplsLabel(label));
421             actionInfos.add(new ActionNxLoadInPort(BigInteger.ZERO));
422         } else if (tunnelType.equals(TunnelTypeVxlan.class)) {
423             actionInfos.add(new ActionSetFieldTunnelId(BigInteger.valueOf(vrfEntry.getL3vni())));
424             LOG.debug("addTunnelInterfaceActions: adding set tunnel id action for prefix {} rd {} l3vni {}"
425                     + " nextHop {} ", vrfEntry.getDestPrefix(), rd, vrfEntry.getL3vni(), nextHopIp);
426             addRewriteDstMacAction(vpnId, vrfEntry, null /*prefixInfo*/, actionInfos);
427         }
428     }
429
430     @Override
431     protected void addRewriteDstMacAction(long vpnId, VrfEntry vrfEntry, @Nullable Prefixes prefixInfo,
432                                           List<ActionInfo> actionInfos) {
433         if (vrfEntry.getGatewayMacAddress() != null) {
434             actionInfos.add(new ActionSetFieldEthernetDestination(actionInfos.size(),
435                     new MacAddress(vrfEntry.getGatewayMacAddress())));
436         }
437     }
438
439 }