Bug 857: Add workaround for a bug of old Open vSwitch. 42/6442/2
authorShigeru Yasuda <s-yasuda@da.jp.nec.com>
Mon, 28 Apr 2014 06:46:33 +0000 (15:46 +0900)
committerShigeru Yasuda <s-yasuda@da.jp.nec.com>
Mon, 28 Apr 2014 08:13:51 +0000 (17:13 +0900)
Old version of OVS has a bug that it may clear OFPFW_DL_VLAN_PCP bit in
FLOW_REMOVED message incorrectly. This workaround tries to remove the SAL
Flow from the flow database after eliminating DL_VLAN_PR match field if
FLOW_REMOVED handler receives a SAL Flow that has DL_VLAN_PR match field.
This should not cause any problem because current VTN Manager never uses
DL_VLAN_PR match field.

Change-Id: I700c61a51459aa86609dde9d5b192a07bb0965d9
Signed-off-by: Shigeru Yasuda <s-yasuda@da.jp.nec.com>
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/VTNFlowDatabase.java
manager/implementation/src/main/java/org/opendaylight/vtn/manager/internal/VTNManagerImpl.java
manager/implementation/src/test/java/org/opendaylight/vtn/manager/internal/VTNFlowDatabaseTest.java

index d11170afc445df93290873fa04f67bc66a232dae..23e15031f147fbe6547b5e8a3544a10c51aeb112 100644 (file)
@@ -9,13 +9,14 @@
 
 package org.opendaylight.vtn.manager.internal;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
-import java.util.ArrayList;
+import java.util.ListIterator;
 import java.util.Map;
-import java.util.HashMap;
 import java.util.Set;
-import java.util.HashSet;
 import java.util.concurrent.ConcurrentMap;
 
 import org.slf4j.Logger;
@@ -29,6 +30,9 @@ import org.opendaylight.vtn.manager.internal.cluster.VTNFlow;
 import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
 import org.opendaylight.controller.sal.core.Node;
 import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchType;
 
 /**
  * Flow database which keeps all VTN flows in the container.
@@ -69,6 +73,40 @@ public class VTNFlowDatabase {
     private final Map<NodeConnector, Set<VTNFlow>>  portFlows =
         new HashMap<NodeConnector, Set<VTNFlow>>();
 
+    /**
+     * Fix broken flow entry originated by Open vSwitch.
+     *
+     * <p>
+     *   Old version of Open vSwitch may clear OFPFW_DL_VLAN_PCP wildcard bit
+     *   in flow entry incorrectly. This method removes VLAN_PCP condition
+     *   from the specified flow entry.
+     * </p>
+     *
+     * @param flow  A {@link Flow} instance converted from an OpenFlow flow
+     *              entry.
+     * @return  A {@link Flow} instance if the specified flow entry contains
+     *          VLAN_PCP condition. {@code false} is returned if not.
+     */
+    public static Flow fixBrokenOvsFlow(Flow flow) {
+        Match match = flow.getMatch();
+        if (match.isPresent(MatchType.DL_VLAN_PR)) {
+            // VTN Manager never set DL_VLAN_PR match field into flow entry.
+            // So we can simply remove it.
+            match.clearField(MatchType.DL_VLAN_PR);
+
+            // We don't need to copy action list.
+            // A Flow instance returned here is configured into a FlowEntry
+            // instance, and it is used as a search key for vtnFlows lookup.
+            // But only flow match and priority affect identity of FlowEntry
+            // instance.
+            Flow f = new Flow(match, null);
+            f.setPriority(flow.getPriority());
+            return f;
+        }
+
+        return null;
+    }
+
     /**
      * Flow entry collector.
      * <p>
@@ -224,11 +262,18 @@ public class VTNFlowDatabase {
      *
      * @param mgr    VTN Manager service.
      * @param entry  A flow entry object which contains expired SAL flow.
+     * @param rmIn   If {@code true} is specified, this method tries to remove
+     *               ingress flow from the forwarding rule manager.
+     * @return  {@code true} is returned if the specified flow was actually
+     *          removed from this instance.
+     *          {@code false} is returned if the specified flow is not
+     *          contained in this instance.
      */
-    public synchronized void flowRemoved(VTNManagerImpl mgr, FlowEntry entry) {
+    public synchronized boolean flowRemoved(VTNManagerImpl mgr,
+                                            FlowEntry entry, boolean rmIn) {
         VTNFlow vflow = vtnFlows.remove(entry);
         if (vflow == null) {
-            return;
+            return false;
         }
 
         // Remove this VTN flow from the database and the cluster cache.
@@ -237,12 +282,12 @@ public class VTNFlowDatabase {
         removeNodeIndex(vflow);
         removePortIndex(vflow);
 
-        Iterator<FlowEntry> it = vflow.getFlowEntries().iterator();
+        ListIterator<FlowEntry> it = vflow.getFlowEntries().listIterator();
         if (!it.hasNext()) {
             // This should never happen.
             LOG.warn("{}: An empty flow expired: group={}",
                      mgr.getContainerName(), gid);
-            return;
+            return true;
         }
 
         FlowEntry ingress = it.next();
@@ -251,11 +296,22 @@ public class VTNFlowDatabase {
                       mgr.getContainerName(), tenantName, gid, ingress);
         }
 
-        if (it.hasNext()) {
-            // Uninstall flow entries except for ingress flow.
+        boolean hasNext;
+        if (rmIn) {
+            // Move iterator cursor backwards in order to remove ingress flow.
+            it.previous();
+            hasNext = true;
+        } else {
+            hasNext = it.hasNext();
+        }
+
+        if (hasNext) {
+            // Uninstall flow entries.
             FlowRemoveTask task = new FlowRemoveTask(mgr, gid, null, it);
             mgr.postFlowTask(task);
         }
+
+        return true;
     }
 
     /**
index 69119d5f2cce9d71fc8d0a0a558ffbb88c3a0a8b..f8fd3b63f55e117e8704ab1e1b761f8d18d3a53b 100644 (file)
@@ -5613,17 +5613,30 @@ public class VTNManagerImpl
         LOG.trace("{}: flowRemoved() called: node={}, flow={}",
                   containerName, node, flow);
 
-        VTNFlowDatabase found = null;
         String empty = "";
         FlowEntry entry = new FlowEntry(empty, empty, flow, node);
         for (VTNFlowDatabase fdb: vtnFlowMap.values()) {
-            if (fdb.containsIngressFlow(entry)) {
-                found = fdb;
-                break;
+            if (fdb.flowRemoved(this, entry, false)) {
+                return;
+            }
+        }
+
+        // Below are workaround for a bug of old version of Open vSwitch.
+        Flow fixedFlow = VTNFlowDatabase.fixBrokenOvsFlow(flow);
+        if (fixedFlow != null) {
+            entry = new FlowEntry(empty, empty, fixedFlow, node);
+            for (VTNFlowDatabase fdb: vtnFlowMap.values()) {
+                // In this case we need to uninstall ingress flow too because
+                // it may be still kept by the forwarding rule manager.
+                if (fdb.flowRemoved(this, entry, true)) {
+                    return;
+                }
             }
         }
-        if (found != null) {
-            found.flowRemoved(this, entry);
+
+        if (flow.getIdleTimeout() != 0) {
+            LOG.trace("{}: Expired flow not found: node={}, flow={}",
+                      containerName, node, flow);
         }
     }
 
index 73ada3ff2b55f8622ffde9163790675093d23fa7..b29dd3dd0b80bd02bfca9b74e54d4779e26c44ec 100644 (file)
@@ -9,6 +9,7 @@
 
 package org.opendaylight.vtn.manager.internal;
 
+import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -33,6 +34,7 @@ import org.opendaylight.vtn.manager.internal.cluster.VlanMapPath;
 import org.opendaylight.controller.forwardingrulesmanager.FlowEntry;
 import org.opendaylight.controller.sal.core.Node;
 import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
 import org.opendaylight.controller.sal.match.Match;
 import org.opendaylight.controller.sal.match.MatchType;
 import org.opendaylight.controller.sal.utils.NetUtils;
@@ -180,7 +182,7 @@ public class VTNFlowDatabaseTest extends TestUseVTNManagerBase {
 
     /**
      * Test method for
-     * {@link VTNFlowDatabase#flowRemoved(VTNManagerImpl, FlowEntry)},
+     * {@link VTNFlowDatabase#flowRemoved(VTNManagerImpl, FlowEntry, boolean)},
      * {@link VTNFlowDatabase#flowRemoved(VTNManagerImpl, FlowGroupId)}.
      */
     @Test
@@ -222,32 +224,47 @@ public class VTNFlowDatabaseTest extends TestUseVTNManagerBase {
         assertEquals(numFlows, db.values().size());
         assertEquals(numFlows * 2, stubObj.getFlowEntries().size());
 
-        // test flowRemoved(VTNMangerImpl, FlowEntry)
+        // test flowRemoved(VTNMangerImpl, FlowEntry, boolean)
+        String empty = "";
+        boolean rmIngress = true;
         for (VTNFlow flow : flows) {
             int i = 0;
             for (FlowEntry ent : flow.getFlowEntries()) {
                 String emsg = ent.toString();
-                fdb.flowRemoved(vtnMgr, ent);
+
+                // Ensure that only node, priority, and flow match affect
+                // identity of FlowEntry instance.
+                Node node = ent.getNode();
+                Flow fl = ent.getFlow();
+                Flow f = new Flow(fl.getMatch(), null);
+                f.setPriority(fl.getPriority());
+                FlowEntry fent = new FlowEntry(empty, empty, f, node);
+                boolean ret = fdb.flowRemoved(vtnMgr, ent, rmIngress);
                 flushFlowTasks();
 
                 if (i == 0) {
+                    assertTrue(ret);
                     VTNFlow regFlow = db.get(flow.getGroupId());
                     assertNull(emsg, regFlow);
                     assertEquals(emsg, numFlows - 1, db.values().size());
-                    assertEquals(emsg, numFlows * 2 - 1,
-                                 stubObj.getFlowEntries().size());
 
-                    // because florwRemoved(VTNManagerImpl, FlowEntry) is invoked
-                    // when VTN flow was expired, this invoked
-                    // after FlowEnry have already been removed.
-                    // In this test case need to remove FlowEntry in DB of stub.
-                    stubObj.uninstallFlowEntry(ent);
+                    if (rmIngress) {
+                        assertEquals(emsg, (numFlows - 1) * 2,
+                                     stubObj.getFlowEntries().size());
+                    } else {
+                        assertEquals(emsg, numFlows * 2 - 1,
+                                     stubObj.getFlowEntries().size());
+                        // Need to uninstall ingress flow because
+                        // flowRemoved() never does.
+                        stubObj.uninstallFlowEntry(ent);
+                    }
 
                     Set<VTNFlow> revert = new HashSet<VTNFlow>();
                     revert.add(flow);
                     revertFlowEntries(vtnMgr, fdb, revert, numFlows,
                                       numFlows * 2);
                 } else {
+                    assertFalse(ret);
                     VTNFlow regFlow = db.get(flow.getGroupId());
                     assertNotNull(emsg, regFlow);
                     assertEquals(emsg, numFlows, db.values().size());
@@ -257,11 +274,12 @@ public class VTNFlowDatabaseTest extends TestUseVTNManagerBase {
 
                 i++;
             }
+            rmIngress = false;
         }
 
         // specify null to FlowEntry.
         FlowEntry fent = null;
-        fdb.flowRemoved(vtnMgr, fent);
+        fdb.flowRemoved(vtnMgr, fent, false);
         flushFlowTasks();
 
         assertEquals(numFlows, db.values().size());
@@ -305,7 +323,7 @@ public class VTNFlowDatabaseTest extends TestUseVTNManagerBase {
 
         VTNFlow flow = flows.iterator().next();
         List<FlowEntry> flowEntries = flow.getFlowEntries();
-        fdb.flowRemoved(vtnMgr, flowEntries.get(0));
+        fdb.flowRemoved(vtnMgr, flowEntries.get(0), false);
         flushFlowTasks();
         assertEquals(0, db.values().size());
         assertEquals(0, stubObj.getFlowEntries().size());
@@ -1083,4 +1101,109 @@ public class VTNFlowDatabaseTest extends TestUseVTNManagerBase {
             assertFalse(fdb.containsIngressFlow(ent));
         }
     }
+
+    /**
+     * Test case for {@link VTNFlowDatabase#fixBrokenOvsFlow(Flow)}.
+     */
+    @Test
+    public void testFixBrokenOvsFlow() {
+        Node node = NodeCreator.createOFNode(Long.valueOf(0L));
+        NodeConnector port = NodeConnectorCreator.
+            createOFNodeConnector(Short.valueOf((short)1), node);
+        NodeConnector outPort = NodeConnectorCreator.
+            createOFNodeConnector(Short.valueOf((short)2), node);
+        byte[] src = {
+            (byte)0x00, (byte)0x11, (byte)0x22,
+            (byte)0x33, (byte)0x44, (byte)0x55,
+        };
+        byte[] dst = {
+            (byte)0xf0, (byte)0xfa, (byte)0xfb,
+            (byte)0xfc, (byte)0xfc, (byte)0xfe,
+        };
+        InetAddress nwSrc = null, nwDst = null;
+        try {
+            nwSrc = InetAddress.getByName("192.168.10.1");
+            nwDst = InetAddress.getByName("10.1.2.254");
+        } catch (Exception e) {
+            unexpected(e);
+        }
+
+        HashMap<MatchType, Object> fields = new HashMap<MatchType, Object>();
+        fields.put(MatchType.IN_PORT, port);
+        fields.put(MatchType.DL_SRC, src);
+        fields.put(MatchType.DL_DST, dst);
+        fields.put(MatchType.DL_VLAN, Short.valueOf((short)10));
+        fields.put(MatchType.DL_OUTER_VLAN, Short.valueOf((short)20));
+        fields.put(MatchType.DL_OUTER_VLAN_PR, Short.valueOf((short)3));
+        fields.put(MatchType.DL_TYPE, Short.valueOf((short)0x800));
+        fields.put(MatchType.NW_TOS, Byte.valueOf((byte)0x1));
+        fields.put(MatchType.NW_PROTO, Byte.valueOf((byte)0x6));
+        fields.put(MatchType.NW_SRC, nwSrc);
+        fields.put(MatchType.NW_DST, nwDst);
+        fields.put(MatchType.TP_SRC, Short.valueOf((short)1000));
+        fields.put(MatchType.TP_DST, Short.valueOf((short)2000));
+
+        Match match = new Match();
+        Match matchPcp = new Match();
+        matchPcp.setField(MatchType.DL_VLAN_PR, Byte.valueOf((byte)0x0));
+        assertFalse(match.equals(matchPcp));
+        assertFalse(match.isPresent(MatchType.DL_VLAN_PR));
+        assertTrue(matchPcp.isPresent(MatchType.DL_VLAN_PR));
+
+        ActionList actions = new ActionList(node);
+        actions.addOutput(outPort).addVlanId((short)100);
+
+        Flow flow = new Flow(match, actions.get());
+        Flow flowPcp = new Flow(matchPcp, actions.get());
+        String group = "flow-group";
+        String name = "flow-name";
+        String empty = "";
+        for (short pri = 0; pri <= 10; pri++) {
+            flow.setPriority(pri);
+            flowPcp.setPriority(pri);
+            assertNull(VTNFlowDatabase.fixBrokenOvsFlow(flow));
+            Flow fixed = VTNFlowDatabase.fixBrokenOvsFlow(flowPcp);
+            assertEquals(pri, fixed.getPriority());
+            assertFalse(fixed.getMatch().isPresent(MatchType.DL_VLAN_PR));
+            assertEquals(match, fixed.getMatch());
+
+            // Ensure that only node, priority, and flow match affect identity
+            // of FlowEntry instance.
+            FlowEntry fent = new FlowEntry(group, name, flow, node);
+            FlowEntry fe = new FlowEntry(empty, empty, fixed, node);
+            assertEquals(fent, fe);
+        }
+
+        for (Map.Entry<MatchType, Object> entry: fields.entrySet()) {
+            MatchType mtype = entry.getKey();
+            Object value = entry.getValue();
+            assertFalse(match.isPresent(mtype));
+            match.setField(mtype, value);
+            assertTrue(match.isPresent(mtype));
+
+            assertFalse(matchPcp.isPresent(mtype));
+            matchPcp.setField(mtype, value);
+            assertTrue(matchPcp.isPresent(mtype));
+            assertFalse(match.equals(matchPcp));
+
+            flow = new Flow(match, null);
+            flowPcp = new Flow(matchPcp, null);
+            for (short pri = 0; pri <= 10; pri++) {
+                flow.setPriority(pri);
+                flowPcp.setPriority(pri);
+                assertNull(VTNFlowDatabase.fixBrokenOvsFlow(flow));
+                Flow fixed = VTNFlowDatabase.fixBrokenOvsFlow(flowPcp);
+                assertEquals(flow, fixed);
+                assertEquals(pri, fixed.getPriority());
+                assertFalse(fixed.getMatch().isPresent(MatchType.DL_VLAN_PR));
+                assertEquals(match, fixed.getMatch());
+
+                // Ensure that only node, priority, and flow match affect
+                // identity of FlowEntry instance.
+                FlowEntry fent = new FlowEntry(group, name, flow, node);
+                FlowEntry fe = new FlowEntry(empty, empty, fixed, node);
+                assertEquals(fent, fe);
+            }
+        }
+    }
 }