Jonathan Hart
Committed by Gerrit Code Review

Fix for flow equality bug which can cause

 * multiple flows with the same match to be simultaneously present in the flow
   store, and
 * similar but different rule in switch and store, resulting in flows stuck in
   PENDING_ADD state.

Fixes ONOS-2426.

Change-Id: I4b4e444c3a6dba7e4d3278e9469069e2dbdb9b67
...@@ -362,6 +362,13 @@ public class DefaultFlowRule implements FlowRule { ...@@ -362,6 +362,13 @@ public class DefaultFlowRule implements FlowRule {
362 } 362 }
363 363
364 @Override 364 @Override
365 + public boolean exactMatch(FlowRule rule) {
366 + return this.equals(rule) &&
367 + Objects.equals(this.id, rule.id()) &&
368 + Objects.equals(this.treatment, rule.treatment());
369 + }
370 +
371 + @Override
365 public String toString() { 372 public String toString() {
366 return toStringHelper(this) 373 return toStringHelper(this)
367 .add("id", Long.toHexString(id.value())) 374 .add("id", Long.toHexString(id.value()))
...@@ -483,7 +490,7 @@ public class DefaultFlowRule implements FlowRule { ...@@ -483,7 +490,7 @@ public class DefaultFlowRule implements FlowRule {
483 } 490 }
484 491
485 private int hash() { 492 private int hash() {
486 - return Objects.hash(deviceId, selector, treatment, tableId); 493 + return Objects.hash(deviceId, priority, selector, tableId);
487 } 494 }
488 495
489 } 496 }
......
...@@ -136,6 +136,34 @@ public interface FlowRule { ...@@ -136,6 +136,34 @@ public interface FlowRule {
136 int tableId(); 136 int tableId();
137 137
138 /** 138 /**
139 + * {@inheritDoc}
140 + *
141 + * Equality for flow rules only considers 'match equality'. This means that
142 + * two flow rules with the same match conditions will be equal, regardless
143 + * of the treatment or other characteristics of the flow.
144 + *
145 + * @param obj the reference object with which to compare.
146 + * @return {@code true} if this object is the same as the obj
147 + * argument; {@code false} otherwise.
148 + */
149 + boolean equals(Object obj);
150 +
151 + /**
152 + * Returns whether this flow rule is an exact match to the flow rule given
153 + * in the argument.
154 + * <p>
155 + * Exact match means that deviceId, priority, selector,
156 + * tableId, flowId and treatment are equal. Note that this differs from
157 + * the notion of object equality for flow rules, which does not consider the
158 + * flowId or treatment when testing equality.
159 + * </p>
160 + *
161 + * @param rule other rule to match against
162 + * @return true if the rules are an exact match, otherwise false
163 + */
164 + boolean exactMatch(FlowRule rule);
165 +
166 + /**
139 * A flowrule builder. 167 * A flowrule builder.
140 */ 168 */
141 interface Builder { 169 interface Builder {
......
...@@ -426,6 +426,11 @@ public class IntentTestsMocks { ...@@ -426,6 +426,11 @@ public class IntentTestsMocks {
426 } 426 }
427 427
428 @Override 428 @Override
429 + public boolean exactMatch(FlowRule rule) {
430 + return this.equals(rule);
431 + }
432 +
433 + @Override
429 public int tableId() { 434 public int tableId() {
430 return tableId; 435 return tableId;
431 } 436 }
......
...@@ -417,12 +417,22 @@ public class FlowRuleManager ...@@ -417,12 +417,22 @@ public class FlowRuleManager
417 417
418 @Override 418 @Override
419 public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) { 419 public void pushFlowMetrics(DeviceId deviceId, Iterable<FlowEntry> flowEntries) {
420 - Set<FlowEntry> storedRules = Sets.newHashSet(store.getFlowEntries(deviceId)); 420 + Map<FlowEntry, FlowEntry> storedRules = Maps.newHashMap();
421 + store.getFlowEntries(deviceId).forEach(f -> storedRules.put(f, f));
422 +
421 for (FlowEntry rule : flowEntries) { 423 for (FlowEntry rule : flowEntries) {
422 try { 424 try {
423 - if (storedRules.remove(rule)) { 425 + FlowEntry storedRule = storedRules.remove(rule);
424 - // we both have the rule, let's update some info then. 426 + if (storedRule != null) {
425 - flowAdded(rule); 427 + if (storedRule.exactMatch(rule)) {
428 + // we both have the rule, let's update some info then.
429 + flowAdded(rule);
430 + } else {
431 + // the two rules are not an exact match - remove the
432 + // switch's rule and install our rule
433 + extraneousFlow(rule);
434 + flowMissing(storedRule);
435 + }
426 } else { 436 } else {
427 // the device has a rule the store does not have 437 // the device has a rule the store does not have
428 if (!allowExtraneousRules) { 438 if (!allowExtraneousRules) {
...@@ -434,7 +444,7 @@ public class FlowRuleManager ...@@ -434,7 +444,7 @@ public class FlowRuleManager
434 continue; 444 continue;
435 } 445 }
436 } 446 }
437 - for (FlowEntry rule : storedRules) { 447 + for (FlowEntry rule : storedRules.keySet()) {
438 try { 448 try {
439 // there are rules in the store that aren't on the switch 449 // there are rules in the store that aren't on the switch
440 log.debug("Adding rule in store, but not on switch {}", rule); 450 log.debug("Adding rule in store, but not on switch {}", rule);
......
...@@ -15,10 +15,11 @@ ...@@ -15,10 +15,11 @@
15 */ 15 */
16 package org.onosproject.rest; 16 package org.onosproject.rest;
17 17
18 -import java.util.HashMap; 18 +import com.eclipsesource.json.JsonArray;
19 -import java.util.HashSet; 19 +import com.eclipsesource.json.JsonObject;
20 -import java.util.Set; 20 +import com.google.common.collect.ImmutableSet;
21 - 21 +import com.sun.jersey.api.client.UniformInterfaceException;
22 +import com.sun.jersey.api.client.WebResource;
22 import org.hamcrest.Description; 23 import org.hamcrest.Description;
23 import org.hamcrest.TypeSafeMatcher; 24 import org.hamcrest.TypeSafeMatcher;
24 import org.junit.After; 25 import org.junit.After;
...@@ -40,6 +41,7 @@ import org.onosproject.net.flow.DefaultTrafficSelector; ...@@ -40,6 +41,7 @@ import org.onosproject.net.flow.DefaultTrafficSelector;
40 import org.onosproject.net.flow.DefaultTrafficTreatment; 41 import org.onosproject.net.flow.DefaultTrafficTreatment;
41 import org.onosproject.net.flow.FlowEntry; 42 import org.onosproject.net.flow.FlowEntry;
42 import org.onosproject.net.flow.FlowId; 43 import org.onosproject.net.flow.FlowId;
44 +import org.onosproject.net.flow.FlowRule;
43 import org.onosproject.net.flow.FlowRuleExtPayLoad; 45 import org.onosproject.net.flow.FlowRuleExtPayLoad;
44 import org.onosproject.net.flow.FlowRuleService; 46 import org.onosproject.net.flow.FlowRuleService;
45 import org.onosproject.net.flow.TrafficSelector; 47 import org.onosproject.net.flow.TrafficSelector;
...@@ -48,11 +50,9 @@ import org.onosproject.net.flow.criteria.Criterion; ...@@ -48,11 +50,9 @@ import org.onosproject.net.flow.criteria.Criterion;
48 import org.onosproject.net.flow.instructions.Instruction; 50 import org.onosproject.net.flow.instructions.Instruction;
49 import org.onosproject.net.flow.instructions.Instructions; 51 import org.onosproject.net.flow.instructions.Instructions;
50 52
51 -import com.eclipsesource.json.JsonArray; 53 +import java.util.HashMap;
52 -import com.eclipsesource.json.JsonObject; 54 +import java.util.HashSet;
53 -import com.google.common.collect.ImmutableSet; 55 +import java.util.Set;
54 -import com.sun.jersey.api.client.UniformInterfaceException;
55 -import com.sun.jersey.api.client.WebResource;
56 56
57 import static org.easymock.EasyMock.anyObject; 57 import static org.easymock.EasyMock.anyObject;
58 import static org.easymock.EasyMock.createMock; 58 import static org.easymock.EasyMock.createMock;
...@@ -194,6 +194,11 @@ public class FlowsResourceTest extends ResourceTest { ...@@ -194,6 +194,11 @@ public class FlowsResourceTest extends ResourceTest {
194 } 194 }
195 195
196 @Override 196 @Override
197 + public boolean exactMatch(FlowRule rule) {
198 + return false;
199 + }
200 +
201 + @Override
197 public FlowRuleExtPayLoad payLoad() { 202 public FlowRuleExtPayLoad payLoad() {
198 return null; 203 return null;
199 } 204 }
......