*/
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;
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;
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
//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))
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;
}
}
}
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;
}
}
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))
.child(Flow.class,newFlowKey).toInstance();
flowBuilder.setKey(newFlowKey);
flowBuilder.addAugmentation(FlowStatisticsData.class, flowStatisticsData.build());
- sucLogger.info("Flow {} is not present in config data store, augmenting statistics as an unaccounted flow",flowBuilder.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();
}
if (statsFlow.getClass() != storedFlow.getClass()) {
return false;
}
- if (statsFlow.getBufferId()== null) {
- if (storedFlow.getBufferId() != null) {
- return false;
- }
- } else if(!statsFlow.getBufferId().equals(storedFlow.getBufferId())) {
- return false;
- }
if (statsFlow.getContainerName()== null) {
if (storedFlow.getContainerName()!= null) {
return false;
} else if(!statsFlow.getContainerName().equals(storedFlow.getContainerName())) {
return false;
}
- if (statsFlow.getCookie()== null) {
- if (storedFlow.getCookie()!= null) {
- return false;
- }
- } else if(!statsFlow.getCookie().equals(storedFlow.getCookie())) {
- return false;
- }
if (statsFlow.getMatch()== null) {
if (storedFlow.getMatch() != null) {
return false;
else if(!matchEquals(statsFlow.getMatch(), storedFlow.getMatch())) {
return false;
}
- if (statsFlow.getHardTimeout() == null) {
- if (storedFlow.getHardTimeout() != null) {
- return false;
- }
- } else if(!statsFlow.getHardTimeout().equals(storedFlow.getHardTimeout() )) {
- return false;
- }
- if (statsFlow.getIdleTimeout()== null) {
- if (storedFlow.getIdleTimeout() != null) {
- return false;
- }
- } else if(!statsFlow.getIdleTimeout().equals(storedFlow.getIdleTimeout())) {
- 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())) {
/**
* 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
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) {
}
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;
+ }
+ }
}
+