Minor bug fix in stale flow statistics data cleanup
[controller.git] / opendaylight / md-sal / statistics-manager / src / main / java / org / opendaylight / controller / md / statistics / manager / StatisticsUpdateCommiter.java
index 907534bcf5a240c8ea661d24d8d086860c1b0bcc..5f264abc2cb41cf68939ec323e54c7064707a515 100644 (file)
@@ -7,12 +7,16 @@
  */
 package org.opendaylight.controller.md.statistics.manager;
 
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.List;
 import java.util.concurrent.ConcurrentMap;
 
 import org.opendaylight.controller.md.statistics.manager.NodeStatisticsAger.FlowEntry;
 import org.opendaylight.controller.md.statistics.manager.NodeStatisticsAger.QueueEntry;
 import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
@@ -88,6 +92,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.nodes.node.meter.MeterStatisticsBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.meter.config.stats.reply.MeterConfigStats;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.types.rev130918.meter.statistics.reply.MeterStats;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.Layer3Match;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.layer._3.match.Ipv4Match;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.statistics.types.rev130925.GenericStatistics;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.FlowCapableNodeConnectorStatisticsData;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.FlowCapableNodeConnectorStatisticsDataBuilder;
@@ -406,8 +412,6 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
                 cache.put(notification.getId(), new NodeStatisticsAger(statisticsManager,key));
             }
             NodeStatisticsAger nsa = cache.get(notification.getId());
-            FlowEntry flowStatsEntry = nsa.new FlowEntry(tableId,flowRule);
-            cache.get(notification.getId()).updateFlowStats(flowStatsEntry);
                 
             //Augment the data to the flow node
 
@@ -445,11 +449,13 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
             //TODO: Not a good way to do it, need to figure out better way.
             //TODO: major issue in any alternate approach is that flow key is incrementally assigned 
             //to the flows stored in data store.
+            // Augment same statistics to all the matching masked flow
             if(table != null){
 
                 for(Flow existingFlow : table.getFlow()){
                     sucLogger.debug("Existing flow in data store : {}",existingFlow.toString());
                     if(flowEquals(flowRule,existingFlow)){
+                        it = this.statisticsManager.startChange();
                         InstanceIdentifier<Flow> flowRef = InstanceIdentifier.builder(Nodes.class).child(Node.class, key)
                                 .augmentation(FlowCapableNode.class)
                                 .child(Table.class, new TableKey(tableId))
@@ -458,9 +464,13 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
                         flowBuilder.addAugmentation(FlowStatisticsData.class, flowStatisticsData.build());
                         sucLogger.debug("Found matching flow in the datastore, augmenting statistics");
                         foundOriginalFlow = true;
+                        // Update entry with timestamp of latest response 
+                        flow.setKey(existingFlow.getKey());
+                        FlowEntry flowStatsEntry = nsa.new FlowEntry(tableId,flow.build());
+                        cache.get(notification.getId()).updateFlowStats(flowStatsEntry);
+
                         it.putOperationalData(flowRef, flowBuilder.build());
                         it.commit();
-                        break;
                     }
                 }
             }
@@ -483,6 +493,12 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
                             flowBuilder.addAugmentation(FlowStatisticsData.class, flowStatisticsData.build());
                             sucLogger.debug("Found matching unaccounted flow in the operational datastore, augmenting statistics");
                             foundOriginalFlow = true;
+                            
+                            // Update entry with timestamp of latest response 
+                            flow.setKey(existingFlow.getKey());
+                            FlowEntry flowStatsEntry = nsa.new FlowEntry(tableId,flow.build());
+                            cache.get(notification.getId()).updateFlowStats(flowStatsEntry);
+
                             it.putOperationalData(flowRef, flowBuilder.build());
                             it.commit();
                             break;
@@ -491,9 +507,9 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
                 }
             }
             if(!foundOriginalFlow){
-                long flowKey = Long.parseLong(new String("1"+Short.toString(tableId)+"0"+Integer.toString(this.unaccountedFlowsCounter)));
+                String flowKey = "#UF$TABLE*"+Short.toString(tableId)+"*"+Integer.toString(this.unaccountedFlowsCounter);
                 this.unaccountedFlowsCounter++;
-                FlowKey newFlowKey = new FlowKey(new FlowId(Long.toString(flowKey)));
+                FlowKey newFlowKey = new FlowKey(new FlowId(flowKey));
                 InstanceIdentifier<Flow> flowRef = InstanceIdentifier.builder(Nodes.class).child(Node.class, key)
                         .augmentation(FlowCapableNode.class)
                         .child(Table.class, new TableKey(tableId))
@@ -501,6 +517,12 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
                 flowBuilder.setKey(newFlowKey);
                 flowBuilder.addAugmentation(FlowStatisticsData.class, flowStatisticsData.build());
                 sucLogger.debug("Flow {} is not present in config data store, augmenting statistics as an unaccounted flow",flowBuilder.build());
+                
+                // Update entry with timestamp of latest response 
+                flow.setKey(newFlowKey);
+                FlowEntry flowStatsEntry = nsa.new FlowEntry(tableId,flow.build());
+                cache.get(notification.getId()).updateFlowStats(flowStatsEntry);
+
                 it.putOperationalData(flowRef, flowBuilder.build());
                 it.commit();
             }
@@ -703,8 +725,8 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
         else if(!matchEquals(statsFlow.getMatch(), storedFlow.getMatch())) {
             return false;
         }
-        if (statsFlow.getPriority() == null) {
-            if (storedFlow.getPriority() != null) {
+        if (storedFlow.getPriority() == null) {
+            if (statsFlow.getPriority() != null && statsFlow.getPriority()!= 0x8000) {
                 return false;
             }
         } else if(!statsFlow.getPriority().equals(storedFlow.getPriority())) {
@@ -722,24 +744,23 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
     
     /**
      * Explicit equals method to compare the 'match' for flows stored in the data-stores and flow fetched from the switch.
-     * Usecase: e.g If user don't set any ethernet source and destination address for match,data store will store null for 
-     * these address.
-     * e.g [_ethernetMatch=EthernetMatch [_ethernetDestination=null, _ethernetSource=null, _ethernetType=
-     * EthernetType [_type=EtherType [_value=2048], _mask=null, augmentation=[]]
+     * Flow installation process has three steps 
+     * 1) Store flow in config data store 
+     * 2) and send it to plugin for installation
+     * 3) Flow gets installed in switch
      * 
-     * But when you fetch the flows from switch, openflow driver library converts all zero bytes of mac address in the 
-     * message stream to 00:00:00:00:00:00. Following string shows how library interpret the zero mac address bytes and 
-     * eventually when translator convert it to MD-SAL match, this is how it looks 
-     * [_ethernetDestination=EthernetDestination [_address=MacAddress [_value=00:00:00:00:00:00], _mask=null, augmentation=[]], 
-     * _ethernetSource=EthernetSource [_address=MacAddress [_value=00:00:00:00:00:00], _mask=null, augmentation=[]], 
-     * _ethernetType=EthernetType [_type=EtherType [_value=2048], _mask=null, augmentation=[]]
+     * The flow user wants to install and what finally gets installed in switch can be slightly different. 
+     * E.g, If user installs flow with src/dst ip=10.0.0.1/24, when it get installed in the switch
+     * src/dst ip will be changes to 10.0.0.0/24 because of netmask of 24. When statistics manager fetch 
+     * stats it gets 10.0.0.0/24 rather then 10.0.0.1/24. Custom match takes care of by using masked ip
+     * while comparing two ip addresses.
      * 
-     * Similarly for inPort, if user/application don't set any value for it, FRM will store null value for it in data store. 
-     * When we fetch the same flow (with its statistics) from switch, plugin converts its value to openflow:X:0.
-     *  e.g _inPort=Uri [_value=openflow:1:0]  
+     * Sometimes when user don't provide few values that is required by flow installation request, like 
+     * priority,hard timeout, idle timeout, cookies etc, plugin usages default values before sending 
+     * request to the switch. So when statistics manager gets flow statistics, it gets the default value.
+     * But the flow stored in config data store don't have those defaults value. I included those checks 
+     * in the customer flow/match equal function. 
      * 
-     * So this custom equals method add additional check to take care of these scenario, in case any match element is null in data-store-flow, but not
-     * in the flow fetched from switch.
      * 
      * @param statsFlow
      * @param storedFlow
@@ -799,7 +820,7 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
             if (statsFlow.getLayer3Match() != null) {
                     return false;
             }
-        } else if(!storedFlow.getLayer3Match().equals(statsFlow.getLayer3Match())) {
+        } else if(!layer3MatchEquals(statsFlow.getLayer3Match(),storedFlow.getLayer3Match())) {
             return false;
         }
         if (storedFlow.getLayer4Match()== null) {
@@ -839,4 +860,100 @@ public class StatisticsUpdateCommiter implements OpendaylightGroupStatisticsList
         }
         return true;
     }
+
+    private boolean layer3MatchEquals(Layer3Match statsLayer3Match, Layer3Match storedLayer3Match){
+
+        if(statsLayer3Match instanceof Ipv4Match && storedLayer3Match instanceof Ipv4Match){
+            Ipv4Match statsIpv4Match = (Ipv4Match)statsLayer3Match;
+            Ipv4Match storedIpv4Match = (Ipv4Match)storedLayer3Match;
+            
+            if (storedIpv4Match.getIpv4Destination()== null) {
+                if (statsIpv4Match.getIpv4Destination()!= null) {
+                    return false;
+                }
+            } else if(!IpAddressEquals(statsIpv4Match.getIpv4Destination(),storedIpv4Match.getIpv4Destination())){
+                return false;
+            }
+            if (storedIpv4Match.getIpv4Source() == null) {
+                if (statsIpv4Match.getIpv4Source() != null) {
+                    return false;
+                }
+            } else if(!IpAddressEquals(statsIpv4Match.getIpv4Source(),storedIpv4Match.getIpv4Source())) {
+                return false;
+            }
+            
+            return true;
+        }else{
+            return storedLayer3Match.equals(statsLayer3Match);
+        }
+    }
+    
+    private boolean IpAddressEquals(Ipv4Prefix statsIpAddress, Ipv4Prefix storedIpAddress) {
+        IntegerIpAddress statsIpAddressInt = StrIpToIntIp(statsIpAddress.getValue());
+        IntegerIpAddress storedIpAddressInt = StrIpToIntIp(storedIpAddress.getValue());
+
+        if(IpAndMaskBasedMatch(statsIpAddressInt,storedIpAddressInt)){
+            return true;
+        }
+        if(IpBasedMatch(statsIpAddressInt,storedIpAddressInt)){
+            return true;
+        }
+        return false;
+    }
+
+    private boolean IpAndMaskBasedMatch(IntegerIpAddress statsIpAddressInt,IntegerIpAddress storedIpAddressInt){
+        return ((statsIpAddressInt.getIp() & statsIpAddressInt.getMask()) ==  (storedIpAddressInt.getIp() & storedIpAddressInt.getMask()));
+    }
+
+    private boolean IpBasedMatch(IntegerIpAddress statsIpAddressInt,IntegerIpAddress storedIpAddressInt){
+        return (statsIpAddressInt.getIp() == storedIpAddressInt.getIp());
+    }
+    
+    /*
+     * Method return integer version of ip address. Converted int will be mask if 
+     * mask specified
+     */
+    private IntegerIpAddress StrIpToIntIp(String ipAddresss){
+        
+        String[] parts = ipAddresss.split("/");
+        String ip = parts[0];
+        int prefix;
+
+        if (parts.length < 2) {
+            prefix = 32;
+        } else {
+            prefix = Integer.parseInt(parts[1]);
+        }
+
+        Inet4Address addr =null;
+        try {
+            addr = (Inet4Address) InetAddress.getByName(ip);
+        } catch (UnknownHostException e){}
+
+        byte[] addrBytes = addr.getAddress();
+        int ipInt = ((addrBytes[0] & 0xFF) << 24) |
+                         ((addrBytes[1] & 0xFF) << 16) |
+                         ((addrBytes[2] & 0xFF) << 8)  |
+                         ((addrBytes[3] & 0xFF) << 0);
+
+        int mask = 0xffffffff << 32 - prefix;
+
+        return new IntegerIpAddress(ipInt, mask);
+    }
+    
+    class IntegerIpAddress{
+        int ip;
+        int mask;
+        public IntegerIpAddress(int ip, int mask) {
+            this.ip = ip;
+            this.mask = mask;
+        }
+        public int getIp() {
+            return ip;
+        }
+        public int getMask() {
+            return mask;
+        }
+    }
 }
+