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