Bug 2369: Fixed NPE in update-flow RPC. 89/12789/4
authorShigeru Yasuda <s-yasuda@da.jp.nec.com>
Wed, 12 Nov 2014 17:13:25 +0000 (02:13 +0900)
committermichal rehak <mirehak@cisco.com>
Fri, 28 Nov 2014 06:35:53 +0000 (06:35 +0000)
  - Need to check whether match in update-flow input is null.
  - Interpret null priority in update-flow input as default priority.
  - Original flow should be removed if any of the following fields is
    changed.
    - idle-timeout
    - hard-timeout
    - flags
    - cookie

Change-Id: I64c4ff66ce4965cbd8c9e9a608082ef2b5a84439
Signed-off-by: Shigeru Yasuda <s-yasuda@da.jp.nec.com>
openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/md/core/sal/OFRpcTaskFactory.java
openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/md/core/sal/convertor/FlowConvertor.java
openflowplugin/src/main/java/org/opendaylight/openflowplugin/openflow/md/util/FlowCreatorUtil.java
openflowplugin/src/test/java/org/opendaylight/openflowplugin/openflow/md/util/FlowCreatorUtilTest.java

index 15043c3c10cd0b12ce4630204f2168b0ddd2389c..72d14a9e299217225ec01133af5556e656f373a2 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ * Copyright (c) 2013-2014 Cisco Systems, Inc. and others.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
@@ -42,6 +42,8 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.Remo
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.RemoveFlowInputBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.UpdateFlowOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.OriginalFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.UpdatedFlow;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAggregateFlowStatisticsFromFlowTableForAllFlowsInput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAggregateFlowStatisticsFromFlowTableForAllFlowsOutput;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.GetAggregateFlowStatisticsFromFlowTableForAllFlowsOutputBuilder;
@@ -298,26 +300,29 @@ public abstract class OFRpcTaskFactory {
                     public ListenableFuture<RpcResult<UpdateFlowOutput>> call() {
                         ListenableFuture<RpcResult<UpdateFlowOutput>> result = null;
 
-                        boolean updatedFlow = (getInput().getUpdatedFlow().getMatch().equals(getInput().getOriginalFlow().getMatch())) &&
-                                (getInput().getUpdatedFlow().getPriority().equals(getInput().getOriginalFlow().getPriority()));
+                        UpdateFlowInput in = getInput();
+                        UpdatedFlow updated = in.getUpdatedFlow();
+                        OriginalFlow original = in.getOriginalFlow();
+                        Short version = getVersion();
 
                         List<FlowModInputBuilder> allFlowMods = new ArrayList<>();
                         List<FlowModInputBuilder> ofFlowModInputs;
 
-                        if (updatedFlow == false) {
-                            // if neither match nor priority matches, then we would need to remove the flow and add it
+                        if (!FlowCreatorUtil.canModifyFlow(original, updated, version)) {
+                            // We would need to remove original and add updated.
+
                             //remove flow
-                            RemoveFlowInputBuilder removeflow = new RemoveFlowInputBuilder(getInput().getOriginalFlow());
+                            RemoveFlowInputBuilder removeflow = new RemoveFlowInputBuilder(original);
                             List<FlowModInputBuilder> ofFlowRemoveInput = FlowConvertor.toFlowModInputs(removeflow.build(),
-                                    getVersion(), getSession().getFeatures().getDatapathId());
+                                    version, getSession().getFeatures().getDatapathId());
                             // remove flow should be the first
                             allFlowMods.addAll(ofFlowRemoveInput);
-                            AddFlowInputBuilder addFlowInputBuilder = new AddFlowInputBuilder(getInput().getUpdatedFlow());
+                            AddFlowInputBuilder addFlowInputBuilder = new AddFlowInputBuilder(updated);
                             ofFlowModInputs = FlowConvertor.toFlowModInputs(addFlowInputBuilder.build(),
-                                    getVersion(), getSession().getFeatures().getDatapathId());
+                                    version, getSession().getFeatures().getDatapathId());
                         } else {
-                            ofFlowModInputs = FlowConvertor.toFlowModInputs(getInput().getUpdatedFlow(),
-                                    getVersion(), getSession().getFeatures().getDatapathId());
+                            ofFlowModInputs = FlowConvertor.toFlowModInputs(updated,
+                                    version, getSession().getFeatures().getDatapathId());
                         }
 
                         allFlowMods.addAll(ofFlowModInputs);
@@ -327,7 +332,7 @@ public abstract class OFRpcTaskFactory {
                         result = OFRpcTaskUtil.chainFutureBarrier(this, result);
                         OFRpcTaskUtil.hookFutureNotification(this, result,
                                 getRpcNotificationProviderService(),
-                                createFlowUpdatedNotification(getInput()));
+                                createFlowUpdatedNotification(in));
                         return result;
                     }
 
@@ -1930,5 +1935,4 @@ public abstract class OFRpcTaskFactory {
         };
         return rpcTask;
     }
-
 }
index b6e37717e6f2279d54161ba77b830634dce954d5..a9ad360cc51245988cb9570f6adfad42a36d92b4 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2013 Ericsson. and others.  All rights reserved.
+ * Copyright (c) 2013-2014 Ericsson. and others.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
@@ -84,12 +84,13 @@ public class FlowConvertor {
     private static final Logger logger = LoggerFactory.getLogger(FlowConvertor.class);
 
     // Default values for when things are null
-    private static final BigInteger DEFAULT_COOKIE = BigInteger.ZERO;
-    private static final BigInteger DEFAULT_COOKIE_MASK = BigInteger.ZERO;
     private static final TableId DEFAULT_TABLE_ID = new TableId(0L);
-    private static final Integer DEFAULT_IDLE_TIMEOUT = 5 * 60;
-    private static final Integer DEFAULT_HARD_TIMEOUT = 10 * 60;
-    private static final Integer DEFAULT_PRIORITY = Integer.parseInt("8000", 16);
+    /** Default idle timeout */
+    public static final Integer DEFAULT_IDLE_TIMEOUT = 5 * 60;
+    /** Default hard timeout */
+    public static final Integer DEFAULT_HARD_TIMEOUT = 10 * 60;
+    /** Default priority */
+    public static final Integer DEFAULT_PRIORITY = Integer.parseInt("8000", 16);
     private static final Long DEFAULT_BUFFER_ID = Long.parseLong("ffffffff", 16);
     private static final Long OFPP_ANY = Long.parseLong("ffffffff", 16);
     private static final Long DEFAULT_OUT_PORT = OFPP_ANY;
@@ -135,13 +136,13 @@ public class FlowConvertor {
         if (flow.getCookie() != null) {
             flowMod.setCookie(flow.getCookie().getValue());
         } else {
-            flowMod.setCookie(DEFAULT_COOKIE);
+            flowMod.setCookie(OFConstants.DEFAULT_COOKIE);
         }
 
         if (flow.getCookieMask() != null) {
             flowMod.setCookieMask(flow.getCookieMask().getValue());
         } else {
-            flowMod.setCookieMask(DEFAULT_COOKIE_MASK);
+            flowMod.setCookieMask(OFConstants.DEFAULT_COOKIE_MASK);
         }
 
         if (flow.getTableId() != null) {
index 6254cc66b21987ac5769ba15ef3f4616a8a9c9dd..663d24f9ad8a7e307caebf188ade37a232be88e4 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright IBM Corporation, 2013.  All rights reserved.
+ * Copyright IBM Corporation and others, 2013-2014.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
@@ -7,9 +7,16 @@
  */
 package org.opendaylight.openflowplugin.openflow.md.util;
 
+import java.math.BigInteger;
+import java.util.Objects;
 import org.opendaylight.openflowplugin.api.OFConstants;
+import org.opendaylight.openflowplugin.openflow.md.core.sal.convertor.FlowConvertor;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.OriginalFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.UpdatedFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.common.types.rev130731.FlowWildcardsV10;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.oxm.rev130731.OxmMatchType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.oxm.rev130731.match.grouping.Match;
@@ -20,6 +27,15 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.protocol.rev130731.multipart.request.multipart.request.body.multipart.request.flow._case.MultipartRequestFlowBuilder;
 
 public final class FlowCreatorUtil {
+    /**
+     * Default FLOW_MOD flags.
+     */
+    public static final FlowModFlags  DEFAULT_FLOW_MOD_FLAGS =
+        new FlowModFlags(FlowConvertor.DEFAULT_OFPFF_CHECK_OVERLAP,
+                         FlowConvertor.DEFAULT_OFPFF_NO_BYT_COUNTS,
+                         FlowConvertor.DEFAULT_OFPFF_NO_PKT_COUNTS,
+                         FlowConvertor.DEFAULT_OFPFF_RESET_COUNTS,
+                         FlowConvertor.DEFAULT_OFPFF_FLOW_REM);
 
     private FlowCreatorUtil() {
         throw new AssertionError("FlowCreatorUtil is not expected to be instantiated.");
@@ -72,4 +88,121 @@ public final class FlowCreatorUtil {
     public static Match createWildcardedMatch() {
         return new MatchBuilder().setType(OxmMatchType.class).build();
     }
+
+    /**
+     * Determine whether a flow entry can be modified or not.
+     *
+     * @param original  An original flow entry.
+     * @param updated   An updated flow entry.
+     * @param version   Protocol version.
+     * @return  {@code true} only if a flow entry can be modified.
+     */
+    public static boolean canModifyFlow(OriginalFlow original,
+                                        UpdatedFlow updated, Short version) {
+        // FLOW_MOD does not change match, priority, idle_timeout, hard_timeout,
+        // flags, and cookie.
+        if (!Objects.equals(original.getMatch(), updated.getMatch()) ||
+            !equalsWithDefault(original.getPriority(), updated.getPriority(),
+                               FlowConvertor.DEFAULT_PRIORITY) ||
+            !equalsWithDefault(original.getIdleTimeout(),
+                               updated.getIdleTimeout(),
+                               FlowConvertor.DEFAULT_IDLE_TIMEOUT) ||
+            !equalsWithDefault(original.getHardTimeout(),
+                               updated.getHardTimeout(),
+                               FlowConvertor.DEFAULT_HARD_TIMEOUT) ||
+            !equalsFlowModFlags(original.getFlags(), updated.getFlags())) {
+            return false;
+        }
+
+        if (!Boolean.TRUE.equals(updated.isStrict()) &&
+            version != null &&
+            version.shortValue() != OFConstants.OFP_VERSION_1_0) {
+            FlowCookie cookieMask = updated.getCookieMask();
+            if (cookieMask != null) {
+                BigInteger mask = cookieMask.getValue();
+                if (mask != null && !mask.equals(BigInteger.ZERO)) {
+                    // Allow FLOW_MOD with filtering by cookie.
+                    return true;
+                }
+            }
+        }
+
+        FlowCookie oc = original.getCookie();
+        FlowCookie uc = updated.getCookie();
+        BigInteger orgCookie;
+        BigInteger updCookie;
+        if (oc == null) {
+            if (uc == null) {
+                return true;
+            }
+
+            orgCookie = OFConstants.DEFAULT_COOKIE;
+            updCookie = uc.getValue();
+        } else {
+            orgCookie = oc.getValue();
+            updCookie = (uc == null)
+                ? OFConstants.DEFAULT_COOKIE : uc.getValue();
+        }
+
+        return equalsWithDefault(orgCookie, updCookie,
+                                 OFConstants.DEFAULT_COOKIE);
+    }
+
+    /**
+     * Return {@code true} only if given two FLOW_MOD flags are identical.
+     *
+     * @param flags1 A value to be compared.
+     * @param flags2 A value to be compared.
+     * @return
+     *   {@code true} only if {@code flags1} and {@code flags2} are identical.
+     */
+    public static boolean equalsFlowModFlags(FlowModFlags flags1,
+                                             FlowModFlags flags2) {
+        FlowModFlags f1;
+        FlowModFlags f2;
+        if (flags1 == null) {
+            if (flags2 == null) {
+                return true;
+            }
+
+            f1 = DEFAULT_FLOW_MOD_FLAGS;
+            f2 = flags2;
+        } else {
+            f1 = flags1;
+            f2 = (flags2 == null) ? DEFAULT_FLOW_MOD_FLAGS : flags2;
+        }
+
+        return equalsWithDefault(f1.isCHECKOVERLAP(), f2.isCHECKOVERLAP(),
+                                 Boolean.FALSE) &&
+            equalsWithDefault(f1.isNOBYTCOUNTS(), f2.isNOBYTCOUNTS(),
+                              Boolean.FALSE) &&
+            equalsWithDefault(f1.isNOPKTCOUNTS(), f2.isNOPKTCOUNTS(),
+                              Boolean.FALSE) &&
+            equalsWithDefault(f1.isRESETCOUNTS(), f2.isRESETCOUNTS(),
+                              Boolean.FALSE) &&
+            equalsWithDefault(f1.isSENDFLOWREM(), f2.isSENDFLOWREM(),
+                              Boolean.FALSE);
+    }
+
+    /**
+     * Return {@code true} only if given two values are identical.
+     *
+     * @param value1 A value to be compared.
+     * @param value2 A value to be compared.
+     * @param def
+     *    Default value. This value is used if {@code null} is passed to
+     *    {@code value1} or {@code value2}.
+     * @param <T> Type of values.
+     * @return
+     *   {@code true} only if {@code value1} and {@code value2} are identical.
+     */
+    public static <T> boolean equalsWithDefault(T value1, T value2, T def) {
+        if (value1 == null) {
+            return value2 == null || value2.equals(def);
+        } else if (value2 == null) {
+            return value1.equals(def);
+        }
+
+        return value1.equals(value2);
+    }
 }
index f42f9d52ad1f9c2dc2936f58efe1cb2cc0caba4d..9b7182a94bfb89ce77638c3a61569416d1ece979 100644 (file)
@@ -8,12 +8,24 @@
 package org.opendaylight.openflowplugin.openflow.md.util;
 
 import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertEquals;
 
+import java.math.BigInteger;
 import org.junit.Test;
 import org.opendaylight.openflowplugin.api.OFConstants;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.OriginalFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.OriginalFlowBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.UpdatedFlow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.flow.update.UpdatedFlowBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.EtherType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.ethernet.match.fields.EthernetTypeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.EthernetMatchBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.oxm.rev130731.OxmMatchType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.oxm.rev130731.match.grouping.Match;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflow.oxm.rev130731.match.v10.grouping.MatchV10;
@@ -61,6 +73,301 @@ public class FlowCreatorUtilTest {
 
     }
 
+    /**
+     * Test method for
+     * {@link FlowCreatorUtil#canModifyFlow(OriginalFlow, UpdatedFlow, Short)}.
+     */
+    @Test
+    public void testCanModifyFlow() {
+        Short of10 = Short.valueOf(OFConstants.OFP_VERSION_1_0);
+        Short of13 = Short.valueOf(OFConstants.OFP_VERSION_1_3);
+        Short[] versions = {null, of10, of13};
+        Boolean[] bools = {null, Boolean.TRUE, Boolean.FALSE};
+
+        Integer defPri = Integer.valueOf(0x8000);
+        Integer defIdle = Integer.valueOf(300);
+        Integer defHard = Integer.valueOf(600);
+        FlowModFlags defFlags = FlowModFlags.getDefaultInstance("sENDFLOWREM");
+        FlowModFlags flags = new FlowModFlags(false, true, false, true, false);
+        FlowCookie defCookie = new FlowCookie(BigInteger.ZERO);
+        FlowCookie cookie = new FlowCookie(BigInteger.valueOf(0x12345L));
+        FlowCookie cookie1 = new FlowCookie(BigInteger.valueOf(0x67890L));
+        FlowCookie cookieMask = new FlowCookie(BigInteger.valueOf(0xffff00L));
+
+        for (Short ver: versions) {
+            OriginalFlowBuilder originalBuilder = new OriginalFlowBuilder();
+            UpdatedFlowBuilder updatedBuilder = new UpdatedFlowBuilder();
+            canModifyFlowTest(true, originalBuilder, updatedBuilder, ver);
+
+            // Default value tests.
+            canModifyFlowTest(true,
+                              new OriginalFlowBuilder().setPriority(defPri),
+                              updatedBuilder, ver);
+            canModifyFlowTest(true, originalBuilder,
+                              new UpdatedFlowBuilder().setPriority(defPri),
+                              ver);
+            canModifyFlowTest(true,
+                              new OriginalFlowBuilder().setIdleTimeout(defIdle),
+                              updatedBuilder, ver);
+            canModifyFlowTest(true, originalBuilder,
+                              new UpdatedFlowBuilder().setIdleTimeout(defIdle),
+                              ver);
+            canModifyFlowTest(true,
+                              new OriginalFlowBuilder().setHardTimeout(defHard),
+                              updatedBuilder, ver);
+            canModifyFlowTest(true, originalBuilder,
+                              new UpdatedFlowBuilder().setHardTimeout(defHard),
+                              ver);
+            canModifyFlowTest(true,
+                              new OriginalFlowBuilder().setFlags(defFlags),
+                              updatedBuilder, ver);
+            canModifyFlowTest(true, originalBuilder,
+                              new UpdatedFlowBuilder().setFlags(defFlags),
+                              ver);
+            canModifyFlowTest(true,
+                              new OriginalFlowBuilder().setCookie(defCookie),
+                              updatedBuilder, ver);
+            canModifyFlowTest(true, originalBuilder,
+                              new UpdatedFlowBuilder().setCookie(defCookie),
+                              ver);
+
+            // Set non-default values.
+            canModifyFlowTest(true,
+                              originalBuilder.setMatch(createMatch(0x800L)),
+                              updatedBuilder.setMatch(createMatch(0x800L)),
+                              ver);
+            canModifyFlowTest(true, originalBuilder.setIdleTimeout(600),
+                              updatedBuilder.setIdleTimeout(600), ver);
+            canModifyFlowTest(true, originalBuilder.setHardTimeout(1200),
+                              updatedBuilder.setHardTimeout(1200), ver);
+            canModifyFlowTest(true, originalBuilder.setPriority(100),
+                              updatedBuilder.setPriority(100), ver);
+            canModifyFlowTest(true, originalBuilder.setFlags(flags),
+                              updatedBuilder.setFlags(flags), ver);
+            canModifyFlowTest(true, originalBuilder.setCookie(cookie),
+                              updatedBuilder.setCookie(cookie), ver);
+
+            OriginalFlow org = originalBuilder.build();
+            UpdatedFlow upd = updatedBuilder.build();
+
+            // Set different match.
+            org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match[] matches = {null, createMatch(0x86ddL)};
+            for (org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match m: matches) {
+                canModifyFlowTest(false, originalBuilder,
+                                  new UpdatedFlowBuilder(upd).setMatch(m),
+                                  ver);
+                canModifyFlowTest(false,
+                                  new OriginalFlowBuilder(org).setMatch(m),
+                                  updatedBuilder, ver);
+            }
+
+            // Set different idle-timeout, hard-timeout, priority.
+            Integer[] integers = {null, Integer.valueOf(3600)};
+            for (Integer i: integers) {
+                canModifyFlowTest(false, originalBuilder,
+                                  new UpdatedFlowBuilder(upd).setIdleTimeout(i),
+                                  ver);
+                canModifyFlowTest(false,
+                                  new OriginalFlowBuilder(org).setIdleTimeout(i),
+                                  updatedBuilder, ver);
+
+                canModifyFlowTest(false, originalBuilder,
+                                  new UpdatedFlowBuilder(upd).setHardTimeout(i),
+                                  ver);
+                canModifyFlowTest(false,
+                                  new OriginalFlowBuilder(org).setHardTimeout(i),
+                                  updatedBuilder, ver);
+
+                canModifyFlowTest(false, originalBuilder,
+                                  new UpdatedFlowBuilder(upd).setPriority(i),
+                                  ver);
+                canModifyFlowTest(false,
+                                  new OriginalFlowBuilder(org).setPriority(i),
+                                  updatedBuilder, ver);
+            }
+
+            // Set different FLOW_MOD flags.
+            FlowModFlags[] flowModFlags = {
+                null,
+                defFlags,
+                new FlowModFlags(true, true, true, true, true),
+            };
+            for (FlowModFlags f: flowModFlags) {
+                canModifyFlowTest(false, originalBuilder,
+                                  new UpdatedFlowBuilder(upd).setFlags(f),
+                                  ver);
+                canModifyFlowTest(false,
+                                  new OriginalFlowBuilder(org).setFlags(f),
+                                  updatedBuilder, ver);
+            }
+
+            // Set different cookie.
+            FlowCookie[] cookies = {
+                null,
+                defCookie,
+                new FlowCookie(BigInteger.valueOf(0x123456L)),
+            };
+            for (FlowCookie c: cookies) {
+                canModifyFlowTest(false, originalBuilder,
+                                  new UpdatedFlowBuilder(upd).setCookie(c),
+                                  ver);
+                canModifyFlowTest(false,
+                                  new OriginalFlowBuilder(org).setCookie(c),
+                                  updatedBuilder, ver);
+            }
+
+            // Cookie mask test.
+            // Cookie mask is used by OF13 non-strict MODIFY command.
+            updatedBuilder.setCookie(cookie1);
+            for (Boolean strict: bools) {
+                updatedBuilder.setCookieMask(null).setStrict(strict);
+                canModifyFlowTest(false, originalBuilder, updatedBuilder, ver);
+
+                updatedBuilder.setCookieMask(defCookie);
+                canModifyFlowTest(false, originalBuilder, updatedBuilder, ver);
+
+                updatedBuilder.setCookieMask(cookieMask);
+                boolean expected = (of13.equals(ver) &&
+                                    !Boolean.TRUE.equals(strict));
+                canModifyFlowTest(expected, originalBuilder, updatedBuilder,
+                                  ver);
+            }
+        }
+    }
+
+    /**
+     * Test method for
+     * {@link FlowCreatorUtil#equalsFlowModFlags(FlowModFlags, FlowModFlags)}.
+     */
+    @Test
+    public void testEqualsFlowModFlags() {
+        FlowModFlags[] defaults = {
+            null,
+            FlowModFlags.getDefaultInstance("sENDFLOWREM"),
+            new FlowModFlags(false, false, false, false, true),
+            new FlowModFlags(false, null, false, null, Boolean.TRUE),
+        };
+        FlowModFlags all = new FlowModFlags(true, true, true, true, true);
+        FlowModFlags none = new FlowModFlags(null, null, null, null, null);
+
+        for (FlowModFlags f: defaults) {
+            assertTrue(FlowCreatorUtil.
+                       equalsFlowModFlags(f, (FlowModFlags)null));
+            assertTrue(FlowCreatorUtil.
+                       equalsFlowModFlags((FlowModFlags)null, f));
+            assertFalse(FlowCreatorUtil.
+                        equalsFlowModFlags((FlowModFlags)null, all));
+            assertFalse(FlowCreatorUtil.
+                        equalsFlowModFlags(all, (FlowModFlags)null));
+            assertFalse(FlowCreatorUtil.
+                        equalsFlowModFlags((FlowModFlags)null, none));
+            assertFalse(FlowCreatorUtil.
+                        equalsFlowModFlags(none, (FlowModFlags)null));
+        }
+
+        String[] bitNames = {
+            "cHECKOVERLAP",
+            "nOBYTCOUNTS",
+            "nOPKTCOUNTS",
+            "rESETCOUNTS",
+            "sENDFLOWREM"
+        };
+        int bit = 0;
+        for (String name: bitNames) {
+            FlowModFlags flags = FlowModFlags.getDefaultInstance(name);
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, all));
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(all, flags));
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, none));
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(none, flags));
+
+            for (String nm: bitNames) {
+                FlowModFlags f = FlowModFlags.getDefaultInstance(nm);
+                boolean expected = nm.equals(name);
+                assertEquals(expected,
+                             FlowCreatorUtil.equalsFlowModFlags(flags, f));
+                assertEquals(expected,
+                             FlowCreatorUtil.equalsFlowModFlags(f, flags));
+            }
+
+            boolean overlap = (bit == 0);
+            boolean noByte = (bit == 1);
+            boolean noPacket = (bit == 2);
+            boolean reset = (bit == 3);
+            boolean flowRem = (bit == 4);
+            FlowModFlags f =
+                new FlowModFlags(overlap, noByte, noPacket, reset, flowRem);
+            assertTrue(FlowCreatorUtil.equalsFlowModFlags(flags, f));
+            assertTrue(FlowCreatorUtil.equalsFlowModFlags(f, flags));
+            assertTrue(FlowCreatorUtil.
+                       equalsFlowModFlags(f, new FlowModFlags(f)));
+
+            f = new FlowModFlags(!overlap, noByte, noPacket, reset, flowRem);
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, f));
+            f = new FlowModFlags(overlap, !noByte, noPacket, reset, flowRem);
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, f));
+            f = new FlowModFlags(overlap, noByte, !noPacket, reset, flowRem);
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, f));
+            f = new FlowModFlags(overlap, noByte, noPacket, !reset, flowRem);
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, f));
+            f = new FlowModFlags(overlap, noByte, noPacket, reset, !flowRem);
+            assertFalse(FlowCreatorUtil.equalsFlowModFlags(flags, f));
+
+            bit++;
+        }
+    }
+
+    /**
+     * Test method for
+     * {@link FlowCreatorUtil#equalsWithDefault(Object, Object, Object)}.
+     */
+    @Test
+    public void testEqualsWithDefault() {
+        // Boolean
+        for (Boolean def: new Boolean[]{Boolean.TRUE, Boolean.FALSE}) {
+            assertTrue(FlowCreatorUtil.equalsWithDefault(null, null, def));
+            assertTrue(FlowCreatorUtil.equalsWithDefault(def, null, def));
+            assertTrue(FlowCreatorUtil.equalsWithDefault(null, def, def));
+
+            Boolean inv = Boolean.valueOf(!def.booleanValue());
+            assertFalse(FlowCreatorUtil.equalsWithDefault(null, inv, def));
+            assertFalse(FlowCreatorUtil.equalsWithDefault(inv, null, def));
+        }
+
+        // Integer
+        Integer[] integers = {
+            Integer.valueOf(-100),
+            Integer.valueOf(0),
+            Integer.valueOf(100),
+        };
+        for (Integer def: integers) {
+            Integer same = new Integer(def.intValue());
+            assertTrue(FlowCreatorUtil.equalsWithDefault(null, null, def));
+            assertTrue(FlowCreatorUtil.equalsWithDefault(same, null, def));
+            assertTrue(FlowCreatorUtil.equalsWithDefault(null, same, def));
+
+            Integer diff = new Integer(def.intValue() +1);
+            assertFalse(FlowCreatorUtil.equalsWithDefault(null, diff, def));
+            assertFalse(FlowCreatorUtil.equalsWithDefault(diff, null, def));
+        }
+
+        // String
+        String[] strings = {
+            "",
+            "test string 1",
+            "test string 2",
+        };
+        for (String def: strings) {
+            String same = new String(def);
+            assertTrue(FlowCreatorUtil.equalsWithDefault(null, null, def));
+            assertTrue(FlowCreatorUtil.equalsWithDefault(same, null, def));
+            assertTrue(FlowCreatorUtil.equalsWithDefault(null, same, def));
+
+            String diff = def + "-1";
+            assertFalse(FlowCreatorUtil.equalsWithDefault(null, diff, def));
+            assertFalse(FlowCreatorUtil.equalsWithDefault(diff, null, def));
+        }
+    }
+
     private void assertMatch(Match match) {
         assertTrue(match.getType().getClass().isInstance(OxmMatchType.class));
     }
@@ -87,4 +394,37 @@ public class FlowCreatorUtilTest {
         assertTrue(matchV10.getTpDst().intValue() == 0);
     }
 
+    /**
+     * Verify that {@link FlowCreatorUtil#canModifyFlow(OriginalFlow, UpdatedFlow, Short)}
+     * returns expected value.
+     *
+     * @param expected
+     *     An expected return value.
+     * @param org
+     *     A original flow builder that contains original flow to be tested.
+     * @param upd
+     *     An updated flow builder that contains updated flow to be tested.
+     * @param version
+     *     OpenFlow protocol version.
+     */
+    private void canModifyFlowTest(boolean expected, OriginalFlowBuilder org,
+                                   UpdatedFlowBuilder upd, Short version) {
+        boolean result = FlowCreatorUtil.
+            canModifyFlow(org.build(), upd.build(), version);
+        assertEquals(expected, result);
+    }
+
+    /**
+     * Create a flow match that specifies ethernet type.
+     *
+     * @param etherType  An ethernet type value.
+     * @return  A flow match that specifies the given ethernet type.
+     */
+    private org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.Match createMatch(long etherType) {
+        EthernetTypeBuilder ethType = new EthernetTypeBuilder().
+            setType(new EtherType(etherType));
+        EthernetMatchBuilder ether = new EthernetMatchBuilder().
+            setEthernetType(ethType.build());
+        return new MatchBuilder().setEthernetMatch(ether.build()).build();
+    }
 }