Merge "Bug 1025: Fixed incorrect revision in sal-remote-augment, which caused log...
[controller.git] / opendaylight / md-sal / statistics-manager / src / main / java / org / opendaylight / controller / md / statistics / manager / impl / helper / FlowComparator.java
1 /*
2  * Copyright IBM Corporation, 2013.  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.controller.md.statistics.manager.impl.helper;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.net.InetAddresses;
12 import java.net.Inet4Address;
13 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
14 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
15 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Flow;
16 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match;
17 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.MacAddressFilter;
18 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatch;
19 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.Layer3Match;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4Match;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * Utility class for comparing flows.
26  */
27 public final class FlowComparator {
28     private final static Logger LOG = LoggerFactory.getLogger(FlowComparator.class);
29
30     private FlowComparator() {
31         throw new UnsupportedOperationException("Utilities class should not be instantiated");
32     }
33
34     public static boolean flowEquals(final Flow statsFlow, final Flow storedFlow) {
35         if (statsFlow == null || storedFlow == null) {
36             return false;
37         }
38         if (statsFlow.getContainerName()== null) {
39             if (storedFlow.getContainerName()!= null) {
40                 return false;
41             }
42         } else if(!statsFlow.getContainerName().equals(storedFlow.getContainerName())) {
43             return false;
44         }
45         if (storedFlow.getPriority() == null) {
46             if (statsFlow.getPriority() != null && statsFlow.getPriority()!= 0x8000) {
47                 return false;
48             }
49         } else if(!statsFlow.getPriority().equals(storedFlow.getPriority())) {
50             return false;
51         }
52         if (statsFlow.getMatch()== null) {
53             if (storedFlow.getMatch() != null) {
54                 return false;
55             }
56         } else if(!matchEquals(statsFlow.getMatch(), storedFlow.getMatch())) {
57             return false;
58         }
59         if (statsFlow.getTableId() == null) {
60             if (storedFlow.getTableId() != null) {
61                 return false;
62             }
63         } else if(!statsFlow.getTableId().equals(storedFlow.getTableId())) {
64             return false;
65         }
66         return true;
67     }
68
69     /**
70      * Explicit equals method to compare the 'match' for flows stored in the data-stores and flow fetched from the switch.
71      * Flow installation process has three steps
72      * 1) Store flow in config data store
73      * 2) and send it to plugin for installation
74      * 3) Flow gets installed in switch
75      *
76      * The flow user wants to install and what finally gets installed in switch can be slightly different.
77      * E.g, If user installs flow with src/dst ip=10.0.0.1/24, when it get installed in the switch
78      * src/dst ip will be changes to 10.0.0.0/24 because of netmask of 24. When statistics manager fetch
79      * stats it gets 10.0.0.0/24 rather then 10.0.0.1/24. Custom match takes care of by using masked ip
80      * while comparing two ip addresses.
81      *
82      * Sometimes when user don't provide few values that is required by flow installation request, like
83      * priority,hard timeout, idle timeout, cookies etc, plugin usages default values before sending
84      * request to the switch. So when statistics manager gets flow statistics, it gets the default value.
85      * But the flow stored in config data store don't have those defaults value. I included those checks
86      * in the customer flow/match equal function.
87      *
88      *
89      * @param statsFlow
90      * @param storedFlow
91      * @return
92      */
93     public static boolean matchEquals(final Match statsFlow, final Match storedFlow) {
94         if (statsFlow == storedFlow) {
95             return true;
96         }
97         if (storedFlow == null && statsFlow != null) return false;
98         if (statsFlow == null && storedFlow != null) return false;
99         if (storedFlow.getEthernetMatch() == null) {
100             if (statsFlow.getEthernetMatch() != null) {
101                 return false;
102             }
103         } else if(!ethernetMatchEquals(statsFlow.getEthernetMatch(),storedFlow.getEthernetMatch())) {
104             return false;
105         }
106         if (storedFlow.getIcmpv4Match()== null) {
107             if (statsFlow.getIcmpv4Match() != null) {
108                 return false;
109             }
110         } else if(!storedFlow.getIcmpv4Match().equals(statsFlow.getIcmpv4Match())) {
111             return false;
112         }
113         if (storedFlow.getIcmpv6Match() == null) {
114             if (statsFlow.getIcmpv6Match() != null) {
115                 return false;
116             }
117         } else if(!storedFlow.getIcmpv6Match().equals(statsFlow.getIcmpv6Match())) {
118             return false;
119         }
120         if (storedFlow.getInPhyPort() == null) {
121             if (statsFlow.getInPhyPort() != null) {
122                 return false;
123             }
124         } else if(!storedFlow.getInPhyPort().equals(statsFlow.getInPhyPort())) {
125             return false;
126         }
127         if (storedFlow.getInPort()== null) {
128             if (statsFlow.getInPort() != null) {
129                 return false;
130             }
131         } else if(!storedFlow.getInPort().equals(statsFlow.getInPort())) {
132             return false;
133         }
134         if (storedFlow.getIpMatch()== null) {
135             if (statsFlow.getIpMatch() != null) {
136                 return false;
137             }
138         } else if(!storedFlow.getIpMatch().equals(statsFlow.getIpMatch())) {
139             return false;
140         }
141         if (storedFlow.getLayer3Match()== null) {
142             if (statsFlow.getLayer3Match() != null) {
143                     return false;
144             }
145         } else if(!layer3MatchEquals(statsFlow.getLayer3Match(),storedFlow.getLayer3Match())) {
146             return false;
147         }
148         if (storedFlow.getLayer4Match()== null) {
149             if (statsFlow.getLayer4Match() != null) {
150                 return false;
151             }
152         } else if(!storedFlow.getLayer4Match().equals(statsFlow.getLayer4Match())) {
153             return false;
154         }
155         if (storedFlow.getMetadata() == null) {
156             if (statsFlow.getMetadata() != null) {
157                 return false;
158             }
159         } else if(!storedFlow.getMetadata().equals(statsFlow.getMetadata())) {
160             return false;
161         }
162         if (storedFlow.getProtocolMatchFields() == null) {
163             if (statsFlow.getProtocolMatchFields() != null) {
164                 return false;
165             }
166         } else if(!storedFlow.getProtocolMatchFields().equals(statsFlow.getProtocolMatchFields())) {
167             return false;
168         }
169         if (storedFlow.getTunnel()== null) {
170             if (statsFlow.getTunnel() != null) {
171                 return false;
172             }
173         } else if(!storedFlow.getTunnel().equals(statsFlow.getTunnel())) {
174             return false;
175         }
176         if (storedFlow.getVlanMatch()== null) {
177             if (statsFlow.getVlanMatch() != null) {
178                 return false;
179             }
180         } else if(!storedFlow.getVlanMatch().equals(statsFlow.getVlanMatch())) {
181             return false;
182         }
183         return true;
184     }
185
186     /*
187      * Custom EthernetMatch is required because mac address string provided by user in EthernetMatch can be in
188      * any case (upper or lower or mix). Ethernet Match which controller receives from switch is always
189      * an upper case string. Default EthernetMatch equals doesn't use equalsIgnoreCase() and hence it fails.
190      * E.g User provided mac address string in flow match is aa:bb:cc:dd:ee:ff and when controller fetch
191      * statistic data, openflow driver library returns AA:BB:CC:DD:EE:FF and default eqauls fails here.
192      */
193     @VisibleForTesting
194     static boolean ethernetMatchEquals(final EthernetMatch statsEthernetMatch, final EthernetMatch storedEthernetMatch){
195         boolean verdict = true;
196         final Boolean checkNullValues = checkNullValues(statsEthernetMatch, storedEthernetMatch);
197         if (checkNullValues != null) {
198             verdict = checkNullValues;
199         } else {
200             if(verdict){
201                 verdict = ethernetMatchFieldsEquals(statsEthernetMatch.getEthernetSource(),storedEthernetMatch.getEthernetSource());
202             }
203             if(verdict){
204                 verdict = ethernetMatchFieldsEquals(statsEthernetMatch.getEthernetDestination(),storedEthernetMatch.getEthernetDestination());
205             }
206             if(verdict){
207                 if(statsEthernetMatch.getEthernetType() == null){
208                     if(storedEthernetMatch.getEthernetType() != null){
209                         verdict = false;
210                     }
211                 }else{
212                     verdict = statsEthernetMatch.getEthernetType().equals(storedEthernetMatch.getEthernetType());
213                 }
214             }
215         }
216         return verdict;
217     }
218
219     private static boolean ethernetMatchFieldsEquals(final MacAddressFilter statsEthernetMatchFields,
220                                                         final MacAddressFilter storedEthernetMatchFields){
221         boolean verdict = true;
222         final Boolean checkNullValues = checkNullValues(statsEthernetMatchFields, storedEthernetMatchFields);
223         if (checkNullValues != null) {
224             verdict = checkNullValues;
225         } else {
226             if(verdict){
227                 verdict = macAddressEquals(statsEthernetMatchFields.getAddress(),storedEthernetMatchFields.getAddress());
228             }
229             if(verdict){
230                 verdict = macAddressEquals(statsEthernetMatchFields.getMask(),storedEthernetMatchFields.getMask());
231             }
232         }
233         return verdict;
234     }
235
236     private static boolean macAddressEquals(final MacAddress statsMacAddress, final MacAddress storedMacAddress){
237         boolean verdict = true;
238         final Boolean checkNullValues = checkNullValues(statsMacAddress, storedMacAddress);
239         if (checkNullValues != null) {
240             verdict = checkNullValues;
241         } else {
242             verdict = statsMacAddress.getValue().equalsIgnoreCase(storedMacAddress.getValue());
243         }
244         return verdict;
245     }
246
247     @VisibleForTesting
248     static boolean layer3MatchEquals(final Layer3Match statsLayer3Match, final Layer3Match storedLayer3Match){
249         boolean verdict = true;
250         if(statsLayer3Match instanceof Ipv4Match && storedLayer3Match instanceof Ipv4Match){
251             final Ipv4Match statsIpv4Match = (Ipv4Match)statsLayer3Match;
252             final Ipv4Match storedIpv4Match = (Ipv4Match)storedLayer3Match;
253
254             if (verdict) {
255                 verdict = compareNullSafe(
256                         storedIpv4Match.getIpv4Destination(), statsIpv4Match.getIpv4Destination());
257             }
258             if (verdict) {
259                 verdict = compareNullSafe(
260                         statsIpv4Match.getIpv4Source(), storedIpv4Match.getIpv4Source());
261             }
262         } else {
263             final Boolean nullCheckOut = checkNullValues(storedLayer3Match, statsLayer3Match);
264             if (nullCheckOut != null) {
265                 verdict = nullCheckOut;
266             } else {
267                 verdict = storedLayer3Match.equals(statsLayer3Match);
268             }
269         }
270
271         return verdict;
272     }
273
274     private static boolean compareNullSafe(final Ipv4Prefix statsIpv4, final Ipv4Prefix storedIpv4) {
275         boolean verdict = true;
276         final Boolean checkDestNullValuesOut = checkNullValues(storedIpv4, statsIpv4);
277         if (checkDestNullValuesOut != null) {
278             verdict = checkDestNullValuesOut;
279         } else if(!IpAddressEquals(statsIpv4, storedIpv4)){
280             verdict = false;
281         }
282
283         return verdict;
284     }
285
286     private static Boolean checkNullValues(final Object v1, final Object v2) {
287         Boolean verdict = null;
288         if (v1 == null && v2 != null) {
289             verdict = Boolean.FALSE;
290         } else if (v1 != null && v2 == null) {
291             verdict = Boolean.FALSE;
292         } else if (v1 == null && v2 == null) {
293             verdict = Boolean.TRUE;
294         }
295
296         return verdict;
297     }
298
299     /**
300      * TODO: why don't we use the default Ipv4Prefix.equals()?
301      *
302      * @param statsIpAddress
303      * @param storedIpAddress
304      * @return true if IPv4prefixes equals
305      */
306     private static boolean IpAddressEquals(final Ipv4Prefix statsIpAddress, final Ipv4Prefix storedIpAddress) {
307         final IntegerIpAddress statsIpAddressInt = StrIpToIntIp(statsIpAddress.getValue());
308         final IntegerIpAddress storedIpAddressInt = StrIpToIntIp(storedIpAddress.getValue());
309
310         if(IpAndMaskBasedMatch(statsIpAddressInt,storedIpAddressInt)){
311             return true;
312         }
313         if(IpBasedMatch(statsIpAddressInt,storedIpAddressInt)){
314             return true;
315         }
316         return false;
317     }
318
319     private static boolean IpAndMaskBasedMatch(final IntegerIpAddress statsIpAddressInt,final IntegerIpAddress storedIpAddressInt){
320         return ((statsIpAddressInt.getIp() & statsIpAddressInt.getMask()) ==  (storedIpAddressInt.getIp() & storedIpAddressInt.getMask()));
321     }
322
323     private static boolean IpBasedMatch(final IntegerIpAddress statsIpAddressInt,final IntegerIpAddress storedIpAddressInt){
324         return (statsIpAddressInt.getIp() == storedIpAddressInt.getIp());
325     }
326
327     /**
328      * Method return integer version of ip address. Converted int will be mask if
329      * mask specified
330      */
331     private static IntegerIpAddress StrIpToIntIp(final String ipAddresss){
332
333         final String[] parts = ipAddresss.split("/");
334         final String ip = parts[0];
335         int prefix;
336
337         if (parts.length < 2) {
338             prefix = 32;
339         } else {
340             prefix = Integer.parseInt(parts[1]);
341         }
342
343         IntegerIpAddress integerIpAddress = null;
344
345             final Inet4Address addr = ((Inet4Address) InetAddresses.forString(ip));
346             final byte[] addrBytes = addr.getAddress();
347             final int ipInt = ((addrBytes[0] & 0xFF) << 24) |
348                     ((addrBytes[1] & 0xFF) << 16) |
349                     ((addrBytes[2] & 0xFF) << 8)  |
350                     ((addrBytes[3] & 0xFF) << 0);
351
352             // FIXME: Is this valid?
353             final int mask = 0xffffffff << 32 - prefix;
354
355             integerIpAddress = new IntegerIpAddress(ipInt, mask);
356
357
358         return integerIpAddress;
359     }
360
361     private static class IntegerIpAddress{
362         int ip;
363         int mask;
364         public IntegerIpAddress(final int ip, final int mask) {
365             this.ip = ip;
366             this.mask = mask;
367         }
368         public int getIp() {
369             return ip;
370         }
371         public int getMask() {
372             return mask;
373         }
374     }
375 }