Support distributed virtual router (DVR) at kubernetes

Change-Id: I6dfa1ad0d2161443e37fa80901d5ababbec6f74e
diff --git a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sRoutingSnatHandler.java b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sRoutingSnatHandler.java
index f16bdda..5fd2758 100644
--- a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sRoutingSnatHandler.java
+++ b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sRoutingSnatHandler.java
@@ -19,6 +19,7 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
@@ -70,6 +71,7 @@
 import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildMoveArpShaToThaExtension;
 import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildMoveArpSpaToTpaExtension;
 import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildMoveEthSrcToDstExtension;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -259,8 +261,14 @@
 
             tBuilder.extension(natTreatment, k8sNode.extBridge())
                     .setEthSrc(k8sNode.extBridgeMac())
-                    .setEthDst(k8sNode.extGatewayMac())
-                    .setOutput(k8sNode.extBridgePortNum());
+                    .setEthDst(k8sNode.extGatewayMac());
+
+            if (MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC).equals(
+                    k8sNode.extGatewayMac())) {
+                tBuilder.setOutput(k8sNode.extIntfPortNum());
+            } else {
+                tBuilder.setOutput(k8sNode.extBridgePortNum());
+            }
         }
 
         k8sFlowRuleService.setRule(
diff --git a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/Constants.java b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/Constants.java
index c32f766..b6382d7 100644
--- a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/Constants.java
+++ b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/Constants.java
@@ -48,4 +48,6 @@
     public static final String DEFAULT_CONFIG_MODE = "NORMAL";
 
     public static final int DEFAULT_SEGMENT_ID = 100;
+    public static final String DEFAULT_EXTERNAL_GATEWAY_MAC = "fa:00:00:00:00:01";
+    public static final String DEFAULT_EXTERNAL_BRIDGE_MAC = "fa:00:00:00:00:02";
 }
diff --git a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sApiConfig.java b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sApiConfig.java
index cbc62e6..9c372b1 100644
--- a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sApiConfig.java
+++ b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sApiConfig.java
@@ -51,11 +51,13 @@
     private final String clientCertData;
     private final String clientKeyData;
     private final Set<HostNodesInfo> infos;
+    private final boolean dvr;
 
     private DefaultK8sApiConfig(String clusterName, int segmentId, IpPrefix extNetworkCidr,
                                 Scheme scheme, IpAddress ipAddress, int port,
                                 Mode mode, State state, String token, String caCertData,
-                                String clientCertData, String clientKeyData, Set<HostNodesInfo> infos) {
+                                String clientCertData, String clientKeyData,
+                                Set<HostNodesInfo> infos, boolean dvr) {
         this.clusterName = clusterName;
         this.segmentId = segmentId;
         this.extNetworkCidr = extNetworkCidr;
@@ -69,6 +71,7 @@
         this.clientCertData = clientCertData;
         this.clientKeyData = clientKeyData;
         this.infos = infos;
+        this.dvr = dvr;
     }
 
     @Override
@@ -132,6 +135,7 @@
                 .clientCertData(clientCertData)
                 .clientKeyData(clientKeyData)
                 .infos(infos)
+                .dvr(dvr)
                 .build();
     }
 
@@ -161,6 +165,11 @@
     }
 
     @Override
+    public boolean dvr() {
+        return dvr;
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
@@ -181,13 +190,14 @@
                 caCertData.equals(that.caCertData) &&
                 clientCertData.equals(that.clientCertData) &&
                 clientKeyData.equals(that.clientKeyData) &&
-                infos.equals(that.infos);
+                infos.equals(that.infos) &&
+                dvr == that.dvr;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(clusterName, segmentId, extNetworkCidr, scheme, ipAddress,
-                port, mode, state, token, caCertData, clientCertData, clientKeyData, infos);
+        return Objects.hash(clusterName, segmentId, extNetworkCidr, scheme, ipAddress, port,
+                mode, state, token, caCertData, clientCertData, clientKeyData, infos, dvr);
     }
 
     @Override
@@ -206,6 +216,7 @@
                 .add("clientCertData", clientCertData)
                 .add("clientKeyData", clientKeyData)
                 .add("infos", infos)
+                .add("dvr", dvr)
                 .toString();
     }
 
@@ -233,6 +244,7 @@
         private String clientCertData;
         private String clientKeyData;
         private Set<HostNodesInfo> infos;
+        private boolean dvr;
 
         @Override
         public K8sApiConfig build() {
@@ -259,7 +271,7 @@
             }
 
             return new DefaultK8sApiConfig(clusterName, segmentId, extNetworkCidr, scheme, ipAddress,
-                    port, mode, state, token, caCertData, clientCertData, clientKeyData, infos);
+                    port, mode, state, token, caCertData, clientCertData, clientKeyData, infos, dvr);
         }
 
         @Override
@@ -339,5 +351,11 @@
             this.infos = infos;
             return this;
         }
+
+        @Override
+        public K8sApiConfig.Builder dvr(boolean dvr) {
+            this.dvr = dvr;
+            return this;
+        }
     }
 }
diff --git a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sNode.java b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sNode.java
index 4e430a3..551a339 100644
--- a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sNode.java
+++ b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/DefaultK8sNode.java
@@ -32,6 +32,8 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_BRIDGE_MAC;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
 import static org.onosproject.k8snode.api.Constants.EXTERNAL_BRIDGE;
 import static org.onosproject.k8snode.api.Constants.GENEVE_TUNNEL;
 import static org.onosproject.k8snode.api.Constants.GRE_TUNNEL;
@@ -414,6 +416,14 @@
     }
 
     @Override
+    public PortNumber extIntfPortNum() {
+        if (this.extIntf == null) {
+            return null;
+        }
+        return portNumber(extBridge, extIntf());
+    }
+
+    @Override
     public MacAddress intgBridgeMac() {
         return macAddress(intgBridge, intgBridgeName());
     }
@@ -425,7 +435,11 @@
 
     @Override
     public MacAddress extBridgeMac() {
-        return macAddress(extBridge, extBridgeName());
+        if (MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC).equals(extGatewayMac())) {
+            return MacAddress.valueOf(DEFAULT_EXTERNAL_BRIDGE_MAC);
+        } else {
+            return macAddress(extBridge, extBridgeName());
+        }
     }
 
     @Override
diff --git a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sApiConfig.java b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sApiConfig.java
index d01f3a2..900290c 100644
--- a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sApiConfig.java
+++ b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sApiConfig.java
@@ -174,6 +174,13 @@
     Set<HostNodesInfo> infos();
 
     /**
+     * Returns the distributed virtual router flag value.
+     *
+     * @return true if it supports DVR, false otherwise
+     */
+    boolean dvr();
+
+    /**
      * Builder of new API config entity.
      */
     interface Builder {
@@ -288,5 +295,13 @@
          * @return kubernetes API config builder
          */
         Builder infos(Set<HostNodesInfo> infos);
+
+        /**
+         * Returns kubernetes API server config builder with supplied DVR flag.
+         *
+         * @param dvr distributed virtual router flag
+         * @return kubernetes API config builder
+         */
+        Builder dvr(boolean dvr);
     }
 }
diff --git a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sNode.java b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sNode.java
index e443880..632f7d2 100644
--- a/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sNode.java
+++ b/apps/k8s-node/api/src/main/java/org/onosproject/k8snode/api/K8sNode.java
@@ -413,6 +413,13 @@
     PortNumber extBridgePortNum();
 
     /**
+     * Returns the external interface (attached to external bridge) port number.
+     *
+     * @return port number, null if the port does ont exist
+     */
+    PortNumber extIntfPortNum();
+
+    /**
      * Returns the integration bridge's MAC address.
      *
      * @return MAC address; null if the MAC address does not exist
diff --git a/apps/k8s-node/api/src/test/java/org/onosproject/k8snode/api/DefaultK8sApiConfigTest.java b/apps/k8s-node/api/src/test/java/org/onosproject/k8snode/api/DefaultK8sApiConfigTest.java
index 22c39e4..c599b9f 100644
--- a/apps/k8s-node/api/src/test/java/org/onosproject/k8snode/api/DefaultK8sApiConfigTest.java
+++ b/apps/k8s-node/api/src/test/java/org/onosproject/k8snode/api/DefaultK8sApiConfigTest.java
@@ -68,6 +68,9 @@
     private static final String CLIENT_KEY_DATA_1 = "clientKeyData1";
     private static final String CLIENT_KEY_DATA_2 = "clientKeyData2";
 
+    private static final boolean DVR_1 = true;
+    private static final boolean DVR_2 = false;
+
     private K8sApiConfig config1;
     private K8sApiConfig sameAsConfig1;
     private K8sApiConfig config2;
@@ -98,6 +101,7 @@
                 .caCertData(CA_CERT_DATA_1)
                 .clientCertData(CLIENT_CERT_DATA_1)
                 .clientKeyData(CLIENT_KEY_DATA_1)
+                .dvr(DVR_1)
                 .build();
 
         sameAsConfig1 = DefaultK8sApiConfig.builder()
@@ -113,6 +117,7 @@
                 .caCertData(CA_CERT_DATA_1)
                 .clientCertData(CLIENT_CERT_DATA_1)
                 .clientKeyData(CLIENT_KEY_DATA_1)
+                .dvr(DVR_1)
                 .build();
 
         config2 = DefaultK8sApiConfig.builder()
@@ -128,6 +133,7 @@
                 .caCertData(CA_CERT_DATA_2)
                 .clientCertData(CLIENT_CERT_DATA_2)
                 .clientKeyData(CLIENT_KEY_DATA_2)
+                .dvr(DVR_2)
                 .build();
     }
 
@@ -160,5 +166,6 @@
         assertEquals(CA_CERT_DATA_1, config.caCertData());
         assertEquals(CLIENT_CERT_DATA_1, config.clientCertData());
         assertEquals(CLIENT_KEY_DATA_1, config.clientKeyData());
+        assertEquals(DVR_1, config.dvr());
     }
 }
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java
index 5b8c05e..9b90c94 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sApiConfigCodec.java
@@ -57,6 +57,7 @@
     private static final String CLIENT_CERT_DATA = "clientCertData";
     private static final String CLIENT_KEY_DATA = "clientKeyData";
     private static final String HOST_NODES_INFO = "hostNodesInfo";
+    private static final String DVR = "dvr";
 
     private static final String MISSING_MESSAGE = " is required in K8sApiConfig";
 
@@ -69,7 +70,8 @@
                 .put(SCHEME, entity.scheme().name())
                 .put(IP_ADDRESS, entity.ipAddress().toString())
                 .put(PORT, entity.port())
-                .put(STATE, entity.state().name());
+                .put(STATE, entity.state().name())
+                .put(DVR, entity.dvr());
 
         if (entity.scheme() == HTTPS) {
             node.put(CA_CERT_DATA, entity.caCertData())
@@ -159,6 +161,11 @@
                 .port(port)
                 .state(DISCONNECTED);
 
+        JsonNode dvrJson = json.get(DVR);
+        if (dvrJson != null) {
+            builder.dvr(dvrJson.asBoolean());
+        }
+
         JsonNode tokenJson = json.get(TOKEN);
         JsonNode caCertDataJson = json.get(CA_CERT_DATA);
         JsonNode clientCertDataJson = json.get(CLIENT_CERT_DATA);
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java
index 8793c46..4cc6609 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/codec/K8sNodeCodec.java
@@ -19,6 +19,7 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.commons.lang.StringUtils;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.k8snode.api.DefaultK8sNode;
@@ -54,6 +55,7 @@
     private static final String EXTERNAL_INTF = "externalInterface";
     private static final String EXTERNAL_BRIDGE_IP = "externalBridgeIp";
     private static final String EXTERNAL_GATEWAY_IP = "externalGatewayIp";
+    private static final String EXTERNAL_GATEWAY_MAC = "externalGatewayMac";
 
     private static final String MISSING_MESSAGE = " is required in K8sNode";
 
@@ -101,6 +103,10 @@
             result.put(EXTERNAL_GATEWAY_IP, node.extGatewayIp().toString());
         }
 
+        if (node.extGatewayMac() != null) {
+            result.put(EXTERNAL_GATEWAY_MAC, node.extGatewayMac().toString());
+        }
+
         return result;
     }
 
@@ -176,6 +182,11 @@
             nodeBuilder.extGatewayIp(IpAddress.valueOf(extGatewayIpJson.asText()));
         }
 
+        JsonNode extGatewayMacJson = json.get(EXTERNAL_GATEWAY_MAC);
+        if (extGatewayMacJson != null) {
+            nodeBuilder.extGatewayMac(MacAddress.valueOf(extGatewayMacJson.asText()));
+        }
+
         log.trace("node is {}", nodeBuilder.build().toString());
 
         return nodeBuilder.build();
diff --git a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java
index c188871..342787c 100644
--- a/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java
+++ b/apps/k8s-node/app/src/main/java/org/onosproject/k8snode/impl/DefaultK8sApiConfigHandler.java
@@ -20,6 +20,7 @@
 import io.fabric8.kubernetes.api.model.NodeAddress;
 import io.fabric8.kubernetes.client.KubernetesClient;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
 import org.onosproject.cluster.NodeId;
@@ -54,6 +55,7 @@
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.k8snode.api.Constants.DEFAULT_CLUSTER_NAME;
+import static org.onosproject.k8snode.api.Constants.DEFAULT_EXTERNAL_GATEWAY_MAC;
 import static org.onosproject.k8snode.api.Constants.EXTERNAL_TO_ROUTER;
 import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
 import static org.onosproject.k8snode.api.K8sNode.Type.MASTER;
@@ -243,7 +245,7 @@
             extBridgeIpStr = annots.get(EXT_BRIDGE_IP);
         }
 
-        return DefaultK8sNode.builder()
+        K8sNode.Builder builder = DefaultK8sNode.builder()
                 .clusterName(DEFAULT_CLUSTER_NAME)
                 .hostname(hostname)
                 .managementIp(managementIp)
@@ -255,8 +257,13 @@
                 .mode(config.mode())
                 .extBridgeIp(IpAddress.valueOf(extBridgeIpStr))
                 .extGatewayIp(IpAddress.valueOf(extGatewayIpStr))
-                .podCidr(node.getSpec().getPodCIDR())
-                .build();
+                .podCidr(node.getSpec().getPodCIDR());
+
+        if (config.dvr()) {
+            builder.extGatewayMac(MacAddress.valueOf(DEFAULT_EXTERNAL_GATEWAY_MAC));
+        }
+
+        return builder.build();
     }
 
     /**
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java
index b5deb38..bf5d337 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigCodecTest.java
@@ -39,6 +39,7 @@
 import java.util.Set;
 
 import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
@@ -103,6 +104,7 @@
                 .clientCertData("clientCertData")
                 .clientKeyData("clientKeyData")
                 .infos(ImmutableSet.of(info))
+                .dvr(true)
                 .build();
 
         ObjectNode configJson = k8sApiConfigCodec.encode(config, context);
@@ -129,6 +131,7 @@
         assertEquals("caCertData", config.caCertData());
         assertEquals("clientCertData", config.clientCertData());
         assertEquals("clientKeyData", config.clientKeyData());
+        assertTrue(config.dvr());
 
         Set<HostNodesInfo> infos = config.infos();
         assertEquals(1, infos.size());
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java
index 7ec3866..22c7117 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sApiConfigJsonMatcher.java
@@ -41,6 +41,7 @@
     private static final String CLIENT_CERT_DATA = "clientCertData";
     private static final String CLIENT_KEY_DATA = "clientKeyData";
     private static final String HOST_NODES_INFO = "hostNodesInfo";
+    private static final String DVR = "dvr";
 
     private K8sApiConfigJsonMatcher(K8sApiConfig k8sApiConfig) {
         this.k8sApiConfig = k8sApiConfig;
@@ -117,6 +118,16 @@
             }
         }
 
+        // check DVR
+        JsonNode jsonDvr = jsonNode.get(DVR);
+        boolean dvr = k8sApiConfig.dvr();
+        if (jsonDvr != null) {
+            if (jsonDvr.asBoolean() != dvr) {
+                description.appendText("DVR was " + jsonDvr);
+                return false;
+            }
+        }
+
         // check token
         JsonNode jsonToken = jsonNode.get(TOKEN);
         String token = k8sApiConfig.token();
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java
index c6861be..6b2689d 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeCodecTest.java
@@ -21,6 +21,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.codec.impl.CodecManager;
@@ -89,6 +90,7 @@
                 .extIntf("eth1")
                 .extBridgeIp(IpAddress.valueOf("10.10.10.5"))
                 .extGatewayIp(IpAddress.valueOf("10.10.10.1"))
+                .extGatewayMac(MacAddress.valueOf("FF:FF:FF:FF:FF:FF"))
                 .build();
 
         ObjectNode nodeJson = k8sNodeCodec.encode(node, context);
@@ -114,6 +116,7 @@
         assertEquals("eth1", node.extIntf());
         assertEquals("172.16.130.5", node.extBridgeIp().toString());
         assertEquals("172.16.130.1", node.extGatewayIp().toString());
+        assertEquals("FF:FF:FF:FF:FF:FF", node.extGatewayMac().toString());
     }
 
     private K8sNode getK8sNode(String resourceName) throws IOException {
diff --git a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java
index 816ca76..71c5ca9 100644
--- a/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java
+++ b/apps/k8s-node/app/src/test/java/org/onosproject/k8snode/codec/K8sNodeJsonMatcher.java
@@ -19,6 +19,7 @@
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeDiagnosingMatcher;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.k8snode.api.K8sNode;
 
 /**
@@ -39,6 +40,7 @@
     private static final String EXTERNAL_INTF = "externalInterface";
     private static final String EXTERNAL_BRIDGE_IP = "externalBridgeIp";
     private static final String EXTERNAL_GATEWAY_IP = "externalGatewayIp";
+    private static final String EXTERNAL_GATEWAY_MAC = "externalGatewayMac";
 
     private K8sNodeJsonMatcher(K8sNode node) {
         this.node = node;
@@ -147,6 +149,16 @@
             }
         }
 
+        // check external gateway MAC
+        JsonNode jsonExtGatewayMac = jsonNode.get(EXTERNAL_GATEWAY_MAC);
+        if (jsonExtGatewayMac != null) {
+            MacAddress extGatewayMac = node.extGatewayMac();
+            if (!jsonExtGatewayMac.asText().equals(extGatewayMac.toString())) {
+                description.appendText("External gateway MAC was " + jsonExtGatewayMac.asText());
+                return false;
+            }
+        }
+
         return true;
     }
 
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json
index 70639b9..8fb9242 100644
--- a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sApiConfig.json
@@ -17,5 +17,6 @@
         "master", "worker"
       ]
     }
-  ]
+  ],
+  "dvr": true
 }
\ No newline at end of file
diff --git a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json
index 495dfee..fb01b77 100644
--- a/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json
+++ b/apps/k8s-node/app/src/test/resources/org/onosproject/k8snode/codec/K8sMinionNode.json
@@ -9,5 +9,6 @@
   "externalBridge": "of:00000000000000b1",
   "externalInterface": "eth1",
   "externalBridgeIp": "172.16.130.5",
-  "externalGatewayIp": "172.16.130.1"
+  "externalGatewayIp": "172.16.130.1",
+  "externalGatewayMac": "FF:FF:FF:FF:FF:FF"
 }
\ No newline at end of file