itm-direct tunnel related changes for scaling
[netvirt.git] / elanmanager / api / src / main / java / org / opendaylight / netvirt / elan / arp / responder / ArpResponderUtil.java
1 /*
2  * Copyright © 2016, 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.elan.arp.responder;
9
10 import java.math.BigInteger;
11 import java.text.MessageFormat;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.atomic.AtomicInteger;
19 import java.util.function.Supplier;
20 import java.util.stream.Collectors;
21
22 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
23 import org.opendaylight.genius.mdsalutil.ActionInfo;
24 import org.opendaylight.genius.mdsalutil.BucketInfo;
25 import org.opendaylight.genius.mdsalutil.FlowEntity;
26 import org.opendaylight.genius.mdsalutil.GroupEntity;
27 import org.opendaylight.genius.mdsalutil.InstructionInfo;
28 import org.opendaylight.genius.mdsalutil.MDSALUtil;
29 import org.opendaylight.genius.mdsalutil.MatchInfo;
30 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
31 import org.opendaylight.genius.mdsalutil.NwConstants;
32 import org.opendaylight.genius.mdsalutil.actions.ActionDrop;
33 import org.opendaylight.genius.mdsalutil.actions.ActionLoadIpToSpa;
34 import org.opendaylight.genius.mdsalutil.actions.ActionLoadMacToSha;
35 import org.opendaylight.genius.mdsalutil.actions.ActionMoveShaToTha;
36 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSourceDestinationEth;
37 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSpaToTpa;
38 import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadInPort;
39 import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
40 import org.opendaylight.genius.mdsalutil.actions.ActionPuntToController;
41 import org.opendaylight.genius.mdsalutil.actions.ActionSetArpOp;
42 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetSource;
43 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
44 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
45 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
46 import org.opendaylight.genius.mdsalutil.matches.MatchArpOp;
47 import org.opendaylight.genius.mdsalutil.matches.MatchArpTpa;
48 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
49 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
50 import org.opendaylight.netvirt.elanmanager.api.ElanHelper;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelInputBuilder;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelOutput;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupTypes;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstance;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.nicira.action.rev140714.add.group.input.buckets.bucket.action.action.NxActionResubmitRpcAddGroupCase;
65 import org.opendaylight.yangtools.yang.common.RpcResult;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 /**
70  * Arp Responder Utility Class.
71  */
72 public final class ArpResponderUtil {
73
74     private static final Logger LOG = LoggerFactory.getLogger(ArpResponderUtil.class);
75
76     private static final long WAIT_TIME_FOR_SYNC_INSTALL = Long.getLong("wait.time.sync.install", 300L);
77
78     /**
79      * A Utility class.
80      */
81     private ArpResponderUtil() {
82     }
83
84     /**
85      * Install Group flow on the DPN.
86      *
87      * @param mdSalManager
88      *            Reference of MDSAL API RPC that provides API for installing
89      *            group flow
90      * @param dpnId
91      *            DPN on which group flow to be installed
92      * @param groupdId
93      *            Uniquely identifiable Group Id for the group flow
94      * @param groupName
95      *            Name of the group flow
96      * @param buckets
97      *            List of the bucket actions for the group flow
98      */
99     public static void installGroup(IMdsalApiManager mdSalManager, BigInteger dpnId, long groupdId, String groupName,
100             List<BucketInfo> buckets) {
101         LOG.trace("Installing group flow on dpn {}", dpnId);
102         GroupEntity groupEntity = MDSALUtil.buildGroupEntity(dpnId, groupdId, groupName, GroupTypes.GroupAll, buckets);
103         mdSalManager.syncInstallGroup(groupEntity);
104         try {
105             Thread.sleep(WAIT_TIME_FOR_SYNC_INSTALL);
106         } catch (InterruptedException e1) {
107             LOG.warn("Error while waiting for ARP Responder Group Entry to be installed on DPN {} ", dpnId);
108         }
109     }
110
111     /**
112      * Get Default ARP Responder Drop flow on the DPN.
113      *
114      * @param dpnId
115      *            DPN on which group flow to be installed
116      */
117     public static FlowEntity getArpResponderTableMissFlow(BigInteger dpnId) {
118         return MDSALUtil.buildFlowEntity(dpnId, NwConstants.ARP_RESPONDER_TABLE,
119                 String.valueOf(NwConstants.ARP_RESPONDER_TABLE), NwConstants.TABLE_MISS_PRIORITY,
120                 ArpResponderConstant.DROP_FLOW_NAME.value(), 0, 0, NwConstants.COOKIE_ARP_RESPONDER,
121                 new ArrayList<MatchInfo>(),
122                 Collections.singletonList(new InstructionApplyActions(Collections.singletonList(new ActionDrop()))));
123     }
124
125     /**
126      * Get Bucket Actions for ARP Responder Group Flow.
127      *
128      * <p>
129      * Install Default Groups, Group has 3 Buckets
130      * </p>
131      * <ul>
132      * <li>Punt to controller</li>
133      * <li>Resubmit to Table {@link NwConstants#LPORT_DISPATCHER_TABLE}, for
134      * ELAN flooding
135      * <li>Resubmit to Table {@link NwConstants#ARP_RESPONDER_TABLE}, for ARP
136      * Auto response from DPN itself</li>
137      * </ul>
138      *
139      * @param resubmitTableId
140      *            Resubmit Flow Table Id
141      * @param resubmitTableId2
142      *            Resubmit Flow Table Id
143      * @return List of bucket actions
144      */
145     public static List<BucketInfo> getDefaultBucketInfos(short resubmitTableId, short resubmitTableId2) {
146         return Arrays.asList(
147                 new BucketInfo(Collections.singletonList(new ActionPuntToController())),
148                 new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId))),
149                 new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId2))));
150     }
151
152     /**
153      * Get Match Criteria for the ARP Responder Flow.
154      *
155      * <p>
156      * List of Match Criteria for ARP Responder
157      * </p>
158      * <ul>
159      * <li>Packet is ARP</li>
160      * <li>Packet is ARP Request</li>
161      * <li>The ARP packet is requesting for Gateway IP</li>
162      * <li>Metadata which is generated by using Service
163      * Index({@link NwConstants#L3VPN_SERVICE_INDEX}) Lport Tag
164      * ({@link MetaDataUtil#METADATA_MASK_LPORT_TAG}) and VRF
165      * ID({@link MetaDataUtil#METADATA_MASK_VRFID})</li>
166      * </ul>
167      *
168      * @param lportTag
169      *            LPort Tag
170      * @param elanInstance
171      *            Elan Instance
172      * @param ipAddress
173      *            Ip Address to be matched to this flow
174      * @return List of Match criteria
175      */
176     public static List<MatchInfo> getMatchCriteria(int lportTag, ElanInstance elanInstance,
177             String ipAddress) {
178
179         BigInteger metadata = ElanHelper.getElanMetadataLabel(elanInstance.getElanTag(), lportTag);
180         BigInteger metadataMask = ElanHelper.getElanMetadataMask();
181         return Arrays.asList(MatchEthernetType.ARP, MatchArpOp.REQUEST, new MatchArpTpa(ipAddress, "32"),
182                 new MatchMetadata(metadata, metadataMask));
183
184     }
185
186     /**
187      * Get List of actions for ARP Responder Flows.
188      *
189      * <p>
190      * Actions consists of all the ARP actions and Resubmit Action to table
191      * {@link NwConstants#ELAN_BASE_TABLE} such that packets can flow ELAN Rule
192      *
193      * @param ipAddress
194      *            IP Address for which ARP Response packet is to be generated
195      * @param macAddress
196      *            MacAddress for which ARP Response packet is to be generated
197      * @return List of ARP Responder Actions actions
198      */
199     public static List<Action> getActions(IInterfaceManager ifaceMgrRpcService, ItmRpcService itmRpcService,
200                                           String ifName, String ipAddress, String macAddress,
201                                           boolean isTunnelInterface) {
202
203         AtomicInteger actionCounter = new AtomicInteger();
204         List<Action> actions = arpActions.apply(actionCounter, macAddress, ipAddress);
205         actions.addAll(getEgressActionsForInterface(ifaceMgrRpcService, itmRpcService, ifName, actionCounter.get(),
206                 isTunnelInterface));
207         LOG.trace("Total Number of actions is {}", actionCounter);
208         return actions;
209
210     }
211
212     /**
213      * A Interface that represent lambda TriFunction.
214      *
215      * @param <T>
216      *            Input type
217      * @param <U>
218      *            Input type
219      * @param <S>
220      *            Input type
221      * @param <R>
222      *            Return Type
223      */
224     @SuppressWarnings("checkstyle:ParameterName")
225     public interface TriFunction<T, U, S, R> {
226         /**
227          * Apply the Action.
228          *
229          * @param t
230          *            Input1
231          * @param u
232          *            Input2
233          * @param s
234          *            Input3
235          * @return computed result
236          */
237         R apply(T t, U u, S s);
238     }
239
240     /**
241      * Lambda to apply arpAction. Inputs action counter, mac address and ip
242      * address
243      */
244     private static TriFunction<AtomicInteger, String, String, List<Action>> arpActions = (actionCounter, mac, ip) -> {
245         List<Action> actions = new ArrayList<>();
246         Collections.addAll(actions, new ActionMoveSourceDestinationEth().buildAction(actionCounter.getAndIncrement()),
247                 new ActionSetFieldEthernetSource(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()),
248                 new ActionSetArpOp(NwConstants.ARP_REPLY).buildAction(actionCounter.getAndIncrement()),
249                 new ActionMoveShaToTha().buildAction(actionCounter.getAndIncrement()),
250                 new ActionMoveSpaToTpa().buildAction(actionCounter.getAndIncrement()),
251                 new ActionLoadMacToSha(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()),
252                 new ActionLoadIpToSpa(ip).buildAction(actionCounter.getAndIncrement()),
253                 new ActionNxLoadInPort(BigInteger.ZERO).buildAction(actionCounter.getAndIncrement()));
254         return actions;
255
256     };
257
258     /**
259      * Get instruction list for ARP responder flows.
260      */
261     public static List<Instruction> getInterfaceInstructions(IInterfaceManager ifaceMgrRpcService, String interfaceName,
262             String ipAddress, String macAddress, ItmRpcService itmRpcService) {
263         List<Action> actions = ArpResponderUtil.getActions(ifaceMgrRpcService, itmRpcService, interfaceName, ipAddress,
264                 macAddress, false);
265         return Collections.singletonList(MDSALUtil.buildApplyActionsInstruction(actions));
266     }
267
268     /**
269      * Get instruction list for ARP responder flows originated from ext-net e.g.
270      * router-gw/fip.<br>
271      * The split-horizon bit should be reset in order to allow traffic from
272      * provider network to be routed back to flat/VLAN network and override the
273      * egress table drop flow.<br>
274      * In order to allow write-metadata in the ARP responder table the resubmit
275      * action needs to be replaced with goto instruction.
276      */
277     public static List<Instruction> getExtInterfaceInstructions(IInterfaceManager ifaceMgrRpcService,
278                                                                 ItmRpcService itmRpcService,
279                                                                 String extInterfaceName, String ipAddress,
280                                                                 String macAddress) {
281         AtomicInteger tableId = new AtomicInteger(-1);
282         List<Instruction> instructions = new ArrayList<>();
283         List<Action> actions = getActions(ifaceMgrRpcService, itmRpcService, extInterfaceName, ipAddress, macAddress,
284                 false);
285         actions.removeIf(v -> {
286             org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action actionClass = v
287                     .getAction();
288             if (actionClass instanceof NxActionResubmitRpcAddGroupCase) {
289                 tableId.set(((NxActionResubmitRpcAddGroupCase) actionClass).getNxResubmit().getTable());
290                 return true;
291             } else {
292                 return false;
293             }
294         });
295
296         instructions.add(MDSALUtil.buildApplyActionsInstruction(actions, 0));
297
298         if (tableId.get() != -1) {
299             // replace resubmit action with goto so it can co-exist with
300             // write-metadata
301             if ((short) tableId.get() > NwConstants.ARP_RESPONDER_TABLE) {
302                 instructions.add(new InstructionGotoTable((short) tableId.get()).buildInstruction(2));
303             } else {
304                 LOG.warn("Failed to insall responder flow for interface {}. Resubmit to {} can't be replaced with goto",
305                         extInterfaceName, tableId);
306             }
307         }
308
309         return instructions;
310     }
311
312     /**
313      * Install ARP Responder FLOW.
314      *
315      * @param mdSalManager
316      *            Reference of MDSAL API RPC that provides API for installing
317      *            flow
318      * @param dpnId
319      *            DPN on which flow to be installed
320      * @param flowId
321      *            Uniquely Identifiable Arp Responder Table flow Id
322      * @param flowName
323      *            Readable flow name
324      * @param priority
325      *            Flow Priority
326      * @param cookie
327      *            Flow Cookie
328      * @param matches
329      *            List of Match Criteria for the flow
330      * @param instructions
331      *            List of Instructions for the flow
332      */
333     public static void installFlow(IMdsalApiManager mdSalManager, BigInteger dpnId, String flowId, String flowName,
334             int priority, BigInteger cookie, List<MatchInfo> matches, List<Instruction> instructions) {
335         Flow flowEntity = MDSALUtil.buildFlowNew(NwConstants.ARP_RESPONDER_TABLE, flowId, priority, flowName, 0, 0,
336                 cookie, matches, instructions);
337         mdSalManager.installFlow(dpnId, flowEntity);
338     }
339
340     /**
341      * Remove flow form DPN.
342      *
343      * @param mdSalManager
344      *            Reference of MDSAL API RPC that provides API for installing
345      *            flow
346      * @param dpnId
347      *            DPN form which flow to be removed
348      * @param flowId
349      *            Uniquely Identifiable Arp Responder Table flow Id that is to
350      *            be removed
351      */
352     public static void removeFlow(IMdsalApiManager mdSalManager, BigInteger dpnId, String flowId) {
353         Flow flowEntity = MDSALUtil.buildFlow(NwConstants.ARP_RESPONDER_TABLE, flowId);
354         mdSalManager.removeFlow(dpnId, flowEntity);
355     }
356
357     /**
358      * Creates Uniquely Identifiable flow Id.
359      *
360      * @param lportTag
361      *            LportTag of the flow
362      * @param ipAdress
363      *            Gateway IP for which ARP Response flow to be installed
364      * @return Unique Flow Id
365      *
366      * @see ArpResponderConstant#FLOW_ID_FORMAT_WITH_LPORT
367      * @see ArpResponderConstant#FLOW_ID_FORMAT_WITHOUT_LPORT
368      */
369     public static String getFlowId(int lportTag, String ipAdress) {
370         return MessageFormat.format(ArpResponderConstant.FLOW_ID_FORMAT_WITH_LPORT.value(),
371                         NwConstants.ARP_RESPONDER_TABLE, lportTag, ipAdress);
372     }
373
374     /**
375      * Generate Cookie per flow.
376      *
377      * <p>
378      * Cookie is generated by Summation of
379      * {@link NwConstants#COOKIE_ARP_RESPONDER} + 1 + lportTag + Gateway IP
380      *
381      * @param lportTag
382      *            Lport Tag of the flow
383      * @param ipAddress
384      *            Gateway IP for which ARP Response flow to be installed
385      * @return Cookie
386      */
387     public static BigInteger generateCookie(int lportTag, String ipAddress) {
388         LOG.trace("IPAddress in long {}", ipAddress);
389         BigInteger cookie = NwConstants.COOKIE_ARP_RESPONDER.add(BigInteger.valueOf(255))
390                 .add(BigInteger.valueOf(ipTolong(ipAddress)));
391         return cookie.add(BigInteger.valueOf(lportTag));
392     }
393
394     private static BigInteger buildCookie(short tableId, int arpOpType) {
395         return NwConstants.COOKIE_ARP_RESPONDER.add(BigInteger.ONE).add(
396                 BigInteger.valueOf(tableId).add(BigInteger.valueOf(arpOpType)));
397     }
398
399     private static String buildFlowRef(short tableId, int arpOpType) {
400         return (tableId == NwConstants.ARP_CHECK_TABLE
401                 ? ArpResponderConstant.FLOWID_PREFIX_FOR_ARP_CHECK.value()
402                 : ArpResponderConstant.FLOWID_PREFIX_FOR_MY_GW_MAC.value()) + tableId + NwConstants.FLOWID_SEPARATOR
403                 + (arpOpType == NwConstants.ARP_REQUEST ? "arp.request" : "arp.replay");
404     }
405
406     public static FlowEntity createArpDefaultFlow(BigInteger dpId, short tableId, int arpOpType,
407             Supplier<List<MatchInfo>> matches, Supplier<List<ActionInfo>> actions) {
408
409         List<InstructionInfo> instructions = Collections.singletonList(new InstructionApplyActions(actions.get()));
410         return MDSALUtil.buildFlowEntity(dpId, tableId, buildFlowRef(tableId, arpOpType),
411                 NwConstants.DEFAULT_ARP_FLOW_PRIORITY, buildFlowRef(tableId, arpOpType), 0, 0,
412                 buildCookie(tableId, arpOpType), matches.get(), instructions);
413     }
414
415     /**
416      * Get IP Address in Long from String.
417      *
418      * @param address
419      *            IP Address that to be converted to long
420      * @return Long value of the IP Address
421      */
422     private static long ipTolong(String address) {
423
424         // Parse IP parts into an int array
425         long[] ip = new long[4];
426         String[] parts = address.split("\\.");
427
428         for (int i = 0; i < 4; i++) {
429             ip[i] = Long.parseLong(parts[i]);
430         }
431         // Add the above IP parts into an int number representing your IP
432         // in a 32-bit binary form
433         long ipNumbers = 0;
434         for (int i = 0; i < 4; i++) {
435             ipNumbers += ip[i] << (24 - (8 * i));
436         }
437         return ipNumbers;
438
439     }
440
441     /**
442      * Get List of Egress Action for the VPN interface.
443      *
444      * @param ifaceMgrRpcService
445      *            Interface Manager RPC reference that invokes API to retrieve
446      *            Egress Action
447      * @param ifName
448      *            VPN Interface for which Egress Action to be retrieved
449      * @param actionCounter
450      *            Action Key
451      * @return List of Egress Actions
452      */
453     public static List<Action> getEgressActionsForInterface(IInterfaceManager ifaceMgrRpcService,
454                                                             ItmRpcService itmRpcService, String ifName,
455                                                             int actionCounter, boolean isTunnelInterface) {
456         if (isTunnelInterface && ifaceMgrRpcService.isItmDirectTunnelsEnabled()) {
457             try {
458                 RpcResult result = itmRpcService.getEgressActionsForTunnel(new GetEgressActionsForTunnelInputBuilder()
459                         .setIntfName(ifName).build()).get();
460                 List<Action> listActions = new ArrayList<>();
461                 if (!result.isSuccessful()) {
462                     LOG.error("getEgressActionsForInterface: RPC Call to Get egress actions for interface {} "
463                             + "returned with Errors {}", ifName, result.getErrors());
464                 } else {
465                     listActions = ((GetEgressActionsForTunnelOutput) result.getResult()).getAction();
466                 }
467                 return listActions;
468             } catch (InterruptedException | ExecutionException e) {
469                 LOG.error("getEgressActionsForInterface: Exception when egress actions for interface {}", ifName, e);
470             }
471         } else {
472             List<ActionInfo> actionInfos = ifaceMgrRpcService.getInterfaceEgressActions(ifName);
473             AtomicInteger counter = new AtomicInteger(actionCounter);
474             return actionInfos.stream().map(v -> v.buildAction(counter.getAndIncrement())).collect(Collectors.toList());
475         }
476         return Collections.emptyList();
477     }
478
479     /**
480      * Uses the IdManager to retrieve ARP Responder GroupId from ELAN pool.
481      *
482      * @param idManager
483      *            the id manager
484      * @return the integer
485      */
486     public static Long retrieveStandardArpResponderGroupId(IdManagerService idManager) {
487
488         AllocateIdInput getIdInput = new AllocateIdInputBuilder()
489                 .setPoolName(ArpResponderConstant.ELAN_ID_POOL_NAME.value())
490                 .setIdKey(ArpResponderConstant.ARP_RESPONDER_GROUP_ID.value()).build();
491
492         try {
493             Future<RpcResult<AllocateIdOutput>> result = idManager.allocateId(getIdInput);
494             RpcResult<AllocateIdOutput> rpcResult = result.get();
495             if (rpcResult.isSuccessful()) {
496                 LOG.trace("Retrieved Group Id is {}", rpcResult.getResult().getIdValue());
497                 return rpcResult.getResult().getIdValue();
498             } else {
499                 LOG.warn("RPC Call to Allocate Id returned with Errors {}", rpcResult.getErrors());
500             }
501         } catch (InterruptedException | ExecutionException e) {
502             LOG.warn("Exception when Allocating Id", e);
503         }
504         return 0L;
505     }
506
507 }