Retrieve MAC address when kubernetes external lb config or interface has only IP address.

Change-Id: I79538f262a1a5d9bceb20d461743212ec6717d5a
(cherry picked from commit 6a83ee6e704e035f8349f93c5bbd8d3b251fe2d5)
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
index 6866537..88ba57c 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/Constants.java
@@ -89,6 +89,7 @@
     public static final int CLI_ID_LENGTH = 30;
     public static final int CLI_NAME_LENGTH = 30;
     public static final int CLI_LONG_NAME_LENGTH = 50;
+    public static final int CLI_LONG_SERVICE_PORT_LENGTH = 100;
     public static final int CLI_IP_ADDRESSES_LENGTH = 50;
     public static final int CLI_IP_ADDRESS_LENGTH = 25;
     public static final int CLI_IP_ADDRESS_AVAILABILITY = 15;
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLb.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLb.java
index 10e7cba..c652d60 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLb.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLb.java
@@ -34,8 +34,7 @@
 
     private final String serviceName;
     private final IpAddress loadbalancerIp;
-    private final Set<Integer> nodePortSet;
-    private final Set<Integer> portSet;
+    private final Set<KubernetesServicePort> servicePorts;
     private final Set<String> endpointSet;
     private final String electedGateway;
     private final IpAddress loadbalancerGwIp;
@@ -43,14 +42,13 @@
     private final String electedWorker;
 
     public DefaultKubernetesExternalLb(String serviceName, IpAddress loadbalancerIp,
-                                       Set<Integer> nodePortSet, Set<Integer> portSet,
+                                      Set<KubernetesServicePort> servicePorts,
                                        Set<String> endpointSet, String electedGateway,
                                        String electedWorker,
                                        IpAddress loadbalancerGwIp, MacAddress loadbalancerGwMac) {
         this.serviceName = serviceName;
         this.loadbalancerIp = loadbalancerIp;
-        this.nodePortSet = nodePortSet;
-        this.portSet = portSet;
+        this.servicePorts = servicePorts;
         this.endpointSet = endpointSet;
         this.electedGateway = electedGateway;
         this.electedWorker = electedWorker;
@@ -69,13 +67,8 @@
     }
 
     @Override
-    public Set<Integer> nodePortSet() {
-        return ImmutableSet.copyOf(nodePortSet);
-    }
-
-    @Override
-    public Set<Integer> portSet() {
-        return ImmutableSet.copyOf(portSet);
+    public Set<KubernetesServicePort> servicePorts() {
+        return ImmutableSet.copyOf(servicePorts);
     }
 
     @Override
@@ -114,8 +107,7 @@
 
         DefaultKubernetesExternalLb that = (DefaultKubernetesExternalLb) o;
         return serviceName.equals(that.serviceName) && loadbalancerIp.equals(that.loadbalancerIp) &&
-                Objects.equals(nodePortSet, that.nodePortSet) &&
-                Objects.equals(portSet, that.portSet) &&
+                Objects.equals(servicePorts, that.servicePorts) &&
                 Objects.equals(endpointSet, that.endpointSet) &&
                 Objects.equals(electedGateway, that.electedGateway) &&
                 Objects.equals(electedWorker, that.electedWorker) &&
@@ -128,8 +120,7 @@
         return DefaultKubernetesExternalLb.builder()
                 .serviceName(serviceName)
                 .loadBalancerIp(loadbalancerIp)
-                .nodePortSet(nodePortSet)
-                .portSet(portSet)
+                .servicePorts(servicePorts)
                 .endpointSet(endpointSet)
                 .electedGateway(electedGateway)
                 .electedWorker(electedWorker)
@@ -143,8 +134,7 @@
         return DefaultKubernetesExternalLb.builder()
                 .serviceName(serviceName)
                 .loadBalancerIp(loadbalancerIp)
-                .nodePortSet(nodePortSet)
-                .portSet(portSet)
+                .servicePorts(servicePorts)
                 .endpointSet(endpointSet)
                 .electedGateway(electedGateway)
                 .electedWorker(electedWorker)
@@ -163,8 +153,7 @@
         return MoreObjects.toStringHelper(this)
                 .add("serviceName", serviceName)
                 .add("loadbalancerIp", loadbalancerIp)
-                .add("nodePort", nodePortSet)
-                .add("port", portSet)
+                .add("servucePorts", servicePorts)
                 .add("endpointSet", endpointSet)
                 .add("electedGateway", electedGateway)
                 .add("electedWorker", electedWorker)
@@ -180,8 +169,7 @@
     public static final class Builder implements KubernetesExternalLb.Builder {
         private String serviceName;
         private IpAddress loadbalancerIp;
-        private Set<Integer> nodePortSet;
-        private Set<Integer> portSet;
+        private Set<KubernetesServicePort> servicePorts;
         private Set<String> endpointSet;
         private String electedGateway;
         private String electedWorker;
@@ -195,11 +183,10 @@
         public KubernetesExternalLb build() {
             checkArgument(serviceName != null, NOT_NULL_MSG, "serviceName");
             checkArgument(loadbalancerIp != null, NOT_NULL_MSG, "loadbalancerIp");
-            checkArgument(!nodePortSet.isEmpty(), NOT_NULL_MSG, "nodePortSet");
-            checkArgument(!portSet.isEmpty(), NOT_NULL_MSG, "portSet");
+            checkArgument(!servicePorts.isEmpty(), NOT_NULL_MSG, "servicePorts");
 
             return new DefaultKubernetesExternalLb(serviceName, loadbalancerIp,
-                    nodePortSet, portSet, endpointSet, electedGateway, electedWorker,
+                    servicePorts, endpointSet, electedGateway, electedWorker,
                     loadbalancerGwip, loadbalancerGwMac);
         }
 
@@ -216,14 +203,8 @@
         }
 
         @Override
-        public Builder nodePortSet(Set<Integer> nodePortSet) {
-            this.nodePortSet = nodePortSet;
-            return this;
-        }
-
-        @Override
-        public Builder portSet(Set<Integer> portSet) {
-            this.portSet = portSet;
+        public Builder servicePorts(Set<KubernetesServicePort> servicePorts) {
+            this.servicePorts = servicePorts;
             return this;
         }
 
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesServicePort.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesServicePort.java
new file mode 100644
index 0000000..c5f2306
--- /dev/null
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesServicePort.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.kubevirtnetworking.api;
+
+import com.google.common.base.MoreObjects;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class DefaultKubernetesServicePort implements KubernetesServicePort {
+    private static final String NOT_EMPTY_MSG = "KubernetesServicePort % cannot be empty";
+
+    private Integer port;
+    private Integer nodePort;
+
+    public DefaultKubernetesServicePort(Integer port, Integer nodePort) {
+        this.port = port;
+        this.nodePort = nodePort;
+    }
+
+    @Override
+    public Integer port() {
+        return port;
+    }
+
+    @Override
+    public Integer nodePort() {
+        return nodePort;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultKubernetesServicePort that = (DefaultKubernetesServicePort) o;
+
+        return Objects.equals(port, that.port) &&
+                Objects.equals(nodePort, that.nodePort);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("port", port.toString())
+                .add("nodePort", nodePort.toString())
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(port, nodePort);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static final class Builder implements KubernetesServicePort.Builder {
+
+        private Integer port;
+        private Integer nodePort;
+
+        private Builder() {
+        }
+
+        @Override
+        public KubernetesServicePort build() {
+            checkArgument(port != null, NOT_EMPTY_MSG, "port");
+            checkArgument(nodePort != null, NOT_EMPTY_MSG, "nodePort");
+
+            return new DefaultKubernetesServicePort(port, nodePort);
+        }
+
+        @Override
+        public KubernetesServicePort.Builder port(Integer port) {
+            this.port = port;
+            return this;
+        }
+
+        @Override
+        public KubernetesServicePort.Builder nodePort(Integer nodePort) {
+            this.nodePort = nodePort;
+            return this;
+        }
+    }
+}
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesExternalLb.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesExternalLb.java
index 4e902b0..6b2eaaa 100644
--- a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesExternalLb.java
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesExternalLb.java
@@ -39,18 +39,11 @@
     IpAddress loadBalancerIp();
 
     /**
-     * Returns the set of node port.
+     * Returns the set of service ports.
      *
      * @return node port
      */
-    Set<Integer> nodePortSet();
-
-    /**
-     * Returns the set of port.
-     *
-     * @return port number
-     */
-    Set<Integer> portSet();
+    Set<KubernetesServicePort> servicePorts();
 
     /**
      * Returns the set of endpoint.
@@ -130,21 +123,14 @@
          */
         Builder loadBalancerIp(IpAddress loadBalancerIp);
 
-        /**
-         * Returns kubernetes external load balancer builder with supplied node port set.
-         *
-         * @param nodePortSet node port set
-         * @return external load balancer builder
-         */
-        Builder nodePortSet(Set<Integer> nodePortSet);
 
         /**
-         * Returns kubernetes external load balancer builder with supplied port set.
+         * Returns kubernetes external load balancer builder with supplied service port set.
          *
-         * @param portSet port set
+         * @param servicePorts service port set
          * @return external load balancer builder
          */
-        Builder portSet(Set<Integer> portSet);
+        Builder servicePorts(Set<KubernetesServicePort> servicePorts);
 
         /**
          * Returns kubernetes external load balancer builder with supplied endpoint set.
diff --git a/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesServicePort.java b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesServicePort.java
new file mode 100644
index 0000000..7afae49
--- /dev/null
+++ b/apps/kubevirt-networking/api/src/main/java/org/onosproject/kubevirtnetworking/api/KubernetesServicePort.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.kubevirtnetworking.api;
+
+/**
+ * Representation of a Kubernetes service port for kubernetes external load balancer.
+ */
+public interface KubernetesServicePort {
+
+    /**
+     * Returns the port.
+     *
+     * @return port
+     */
+    Integer port();
+
+    /**
+     * Returns the node port.
+     *
+     * @return node port
+     */
+    Integer nodePort();
+
+    interface Builder {
+
+        /**
+         * Builds immutable kubernetes service port instance.
+         *
+         * @return kubernetes service port instance
+         */
+        KubernetesServicePort build();
+
+        /**
+         * Returns kubernetes service port builder with supplied port.
+         *
+         * @param port port
+         * @return kubernetes service port builder
+         */
+        Builder port(Integer port);
+
+        /**
+         * Returns kubernetes service port builder with supplied node port.
+         *
+         * @param nodePort node port
+         * @return kubernetes service port builder
+         */
+        Builder nodePort(Integer nodePort);
+    }
+}
diff --git a/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLbTest.java b/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLbTest.java
index 228bb9f..ea50fef 100644
--- a/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLbTest.java
+++ b/apps/kubevirt-networking/api/src/test/java/org/onosproject/kubevirtnetworking/api/DefaultKubernetesExternalLbTest.java
@@ -35,10 +35,16 @@
     private static final String SERVICE_NAME_2 = "service_name_2";
     private static final IpAddress LOADBALANCER_IP_1 = IpAddress.valueOf("1.1.1.2");
     private static final IpAddress LOADBALANCER_IP_2 = IpAddress.valueOf("2.2.2.2");
-    private static final Set<Integer> NODE_PORT_SET_1 = Sets.newHashSet(Integer.valueOf(32080));
-    private static final Set<Integer> NODE_PORT_SET_2 = Sets.newHashSet(Integer.valueOf(33080));
-    private static final Set<Integer> PORT_SET_1 = Sets.newHashSet(Integer.valueOf(8080));
-    private static final Set<Integer> PORT_SET_2 = Sets.newHashSet(Integer.valueOf(9090));
+    private static final KubernetesServicePort SERVICE_PORT_1 = DefaultKubernetesServicePort.builder()
+            .port(Integer.valueOf(8080))
+            .nodePort(Integer.valueOf(31080))
+            .build();
+    private static final KubernetesServicePort SERVICE_PORT_2 = DefaultKubernetesServicePort.builder()
+            .port(Integer.valueOf(8081))
+            .nodePort(Integer.valueOf(31081))
+            .build();
+    private static final Set<KubernetesServicePort> SERVICE_PORT_SET_1 = Sets.newHashSet(SERVICE_PORT_1);
+    private static final Set<KubernetesServicePort> SERVICE_PORT_SET_2 = Sets.newHashSet(SERVICE_PORT_2);
     private static final Set<String> ENDPOINT_SET_1 = Sets.newHashSet(String.valueOf("1.1.2.1"));
     private static final Set<String> ENDPOINT_SET_2 = Sets.newHashSet(String.valueOf("1.1.2.2"));
     private static final String ELECTED_GATEWAY_1 = "gateway1";
@@ -71,8 +77,7 @@
         lb1 = DefaultKubernetesExternalLb.builder()
                 .serviceName(SERVICE_NAME_1)
                 .loadBalancerIp(LOADBALANCER_IP_1)
-                .nodePortSet(NODE_PORT_SET_1)
-                .portSet(PORT_SET_1)
+                .servicePorts(SERVICE_PORT_SET_1)
                 .endpointSet(ENDPOINT_SET_1)
                 .electedGateway(ELECTED_GATEWAY_1)
                 .electedWorker(ELECTED_WORKER_1)
@@ -83,8 +88,7 @@
         sameAsLb1 = DefaultKubernetesExternalLb.builder()
                 .serviceName(SERVICE_NAME_1)
                 .loadBalancerIp(LOADBALANCER_IP_1)
-                .nodePortSet(NODE_PORT_SET_1)
-                .portSet(PORT_SET_1)
+                .servicePorts(SERVICE_PORT_SET_1)
                 .endpointSet(ENDPOINT_SET_1)
                 .electedGateway(ELECTED_GATEWAY_1)
                 .electedWorker(ELECTED_WORKER_1)
@@ -95,8 +99,7 @@
         lb2 = DefaultKubernetesExternalLb.builder()
                 .serviceName(SERVICE_NAME_2)
                 .loadBalancerIp(LOADBALANCER_IP_2)
-                .nodePortSet(NODE_PORT_SET_2)
-                .portSet(PORT_SET_2)
+                .servicePorts(SERVICE_PORT_SET_2)
                 .endpointSet(ENDPOINT_SET_2)
                 .electedGateway(ELECTED_GATEWAY_2)
                 .electedWorker(ELECTED_WORKER_2)
@@ -124,8 +127,7 @@
 
         assertEquals(SERVICE_NAME_1, lb.serviceName());
         assertEquals(LOADBALANCER_IP_1, lb.loadBalancerIp());
-        assertEquals(NODE_PORT_SET_1, lb.nodePortSet());
-        assertEquals(PORT_SET_1, lb.portSet());
+        assertEquals(SERVICE_PORT_SET_1, lb.servicePorts());
         assertEquals(ENDPOINT_SET_1, lb.endpointSet());
     }
 }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubernetesListServiceCommand.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubernetesListServiceCommand.java
index 47d4a71..fcb7188 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubernetesListServiceCommand.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/cli/KubernetesListServiceCommand.java
@@ -27,6 +27,7 @@
 import java.util.List;
 
 import static org.onosproject.kubevirtnetworking.api.Constants.CLI_IP_ADDRESS_LENGTH;
+import static org.onosproject.kubevirtnetworking.api.Constants.CLI_LONG_SERVICE_PORT_LENGTH;
 import static org.onosproject.kubevirtnetworking.api.Constants.CLI_MAC_ADDRESS_LENGTH;
 import static org.onosproject.kubevirtnetworking.api.Constants.CLI_MARGIN_LENGTH;
 import static org.onosproject.kubevirtnetworking.api.Constants.CLI_NAME_LENGTH;
@@ -47,11 +48,12 @@
         List<KubernetesExternalLb> elbList = Lists.newArrayList(service.loadBalancers());
 
         String format = genFormatString(ImmutableList.of(CLI_NAME_LENGTH, CLI_IP_ADDRESS_LENGTH,
-                CLI_NAME_LENGTH, CLI_NAME_LENGTH, CLI_IP_ADDRESS_LENGTH, CLI_MAC_ADDRESS_LENGTH));
+                CLI_NAME_LENGTH, CLI_NAME_LENGTH, CLI_IP_ADDRESS_LENGTH, CLI_MAC_ADDRESS_LENGTH,
+                CLI_LONG_SERVICE_PORT_LENGTH));
 
 
         print(format, "Service Name", "Loadbalancer IP", "Elected Gateway", "Elected Worker",
-                "Loadbalancer GW IP", "Loadbalancer GW MAC");
+                "Loadbalancer GW IP", "Loadbalancer GW MAC", "Service Port");
 
         for (KubernetesExternalLb elb : elbList) {
             String lbIp = elb.loadBalancerIp() == null ? "N/A" : elb.loadBalancerIp().toString();
@@ -59,6 +61,7 @@
             String electedWorker = elb.electedWorker() == null ? "N/A" : elb.electedWorker();
             String lbGwIp = elb.loadBalancerGwIp() == null ? "N/A" : elb.loadBalancerGwIp().toString();
             String lbGwMac = elb.loadBalancerGwMac() == null ? "N/A" : elb.loadBalancerGwMac().toString();
+            String lbServicePort = elb.servicePorts().isEmpty() ? "N/A" : elb.servicePorts().toString();
 
             print(format, StringUtils.substring(elb.serviceName(), 0,
                     CLI_NAME_LENGTH - CLI_MARGIN_LENGTH),
@@ -71,7 +74,9 @@
                     StringUtils.substring(lbGwIp, 0,
                             CLI_IP_ADDRESS_LENGTH - CLI_MARGIN_LENGTH),
                     StringUtils.substring(lbGwMac, 0,
-                            CLI_MAC_ADDRESS_LENGTH - CLI_MARGIN_LENGTH)
+                            CLI_MAC_ADDRESS_LENGTH - CLI_MARGIN_LENGTH),
+                    StringUtils.substring(lbServicePort, 0,
+                            CLI_LONG_SERVICE_PORT_LENGTH - CLI_MARGIN_LENGTH)
             );
         }
     }
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubernetesExternalLbStore.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubernetesExternalLbStore.java
index 1bd286e..ce78884 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubernetesExternalLbStore.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/DistributedKubernetesExternalLbStore.java
@@ -21,10 +21,12 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.kubevirtnetworking.api.DefaultKubernetesExternalLb;
+import org.onosproject.kubevirtnetworking.api.DefaultKubernetesServicePort;
 import org.onosproject.kubevirtnetworking.api.KubernetesExternalLb;
 import org.onosproject.kubevirtnetworking.api.KubernetesExternalLbEvent;
 import org.onosproject.kubevirtnetworking.api.KubernetesExternalLbStore;
 import org.onosproject.kubevirtnetworking.api.KubernetesExternalLbStoreDelegate;
+import org.onosproject.kubevirtnetworking.api.KubernetesServicePort;
 import org.onosproject.store.AbstractStore;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
@@ -72,6 +74,8 @@
             .register(KryoNamespaces.API)
             .register(KubernetesExternalLb.class)
             .register(DefaultKubernetesExternalLb.class)
+            .register(KubernetesServicePort.class)
+            .register(DefaultKubernetesServicePort.class)
             .register(IpAddress.class)
             .register(Collection.class)
             .build();
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbArpHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbArpHandler.java
new file mode 100644
index 0000000..5af9834
--- /dev/null
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbArpHandler.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2022-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.kubevirtnetworking.impl;
+
+import org.onlab.packet.ARP;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.kubevirtnetworking.api.KubevirtFlowRuleService;
+import org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfig;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigAdminService;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigListener;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbInterface;
+import org.onosproject.kubevirtnode.api.KubevirtNode;
+import org.onosproject.kubevirtnode.api.KubevirtNodeAdminService;
+import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
+import org.onosproject.kubevirtnode.api.KubevirtNodeListener;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.nio.ByteBuffer;
+import java.util.Objects;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.kubevirtnetworking.api.Constants.GW_ENTRY_TABLE;
+import static org.onosproject.kubevirtnetworking.api.Constants.KUBERNETES_EXTERNAL_LB_FAKE_MAC;
+import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
+import static org.onosproject.kubevirtnetworking.api.Constants.PRIORITY_ARP_GATEWAY_RULE;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.kubernetesElbMac;
+import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Handles arp packets related to the kubernetes external loadbalancer handler.
+ */
+@Component(immediate = true)
+public class KubernetesExternalLbArpHandler {
+    protected final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected ClusterService clusterService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected LeadershipService leadershipService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubernetesExternalLbConfigAdminService externalLbConfigAdminService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtNodeAdminService nodeAdminService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected KubevirtFlowRuleService kubevirtFlowRuleService;
+
+    private static final IpAddress NON_ROUTABLE_META_ADDRESS = IpAddress.valueOf("0.0.0.0");
+    private final Timer externalLbGwTimer = new Timer("kubernetes-external-lb-gateway");
+    private final Timer externalLbIntfGwTimer = new Timer("kubernetes-external-lb-intf-gateway");
+    private static final long SECONDS = 1000L;
+    private static final long INITIAL_DELAY = 5 * SECONDS;
+    private static final long TASK_PERIOD = 60 * SECONDS;
+
+    private final ExecutorService eventExecutor = newSingleThreadExecutor(
+            groupedThreads(this.getClass().getSimpleName(), "event-handler"));
+
+    private final PacketProcessor packetProcessor = new InternalPacketProcessor();
+    private final InternalKubernetesExternalLbConfigListener
+            lbConfigListener = new InternalKubernetesExternalLbConfigListener();
+
+    private final InternalNodeEventListener
+            nodeEventListener = new InternalNodeEventListener();
+
+
+    private ApplicationId appId;
+    private NodeId localNodeId;
+
+    @Activate
+    protected void activate() {
+        appId = coreService.registerApplication(KUBEVIRT_NETWORKING_APP_ID);
+        localNodeId = clusterService.getLocalNode().id();
+        leadershipService.runForLeadership(appId.name());
+
+        packetService.addProcessor(packetProcessor, PacketProcessor.director(1));
+        externalLbConfigAdminService.addListener(lbConfigListener);
+        nodeAdminService.addListener(nodeEventListener);
+
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        leadershipService.withdraw(appId.name());
+        packetService.removeProcessor(packetProcessor);
+        externalLbConfigAdminService.removeListener(lbConfigListener);
+        nodeAdminService.removeListener(nodeEventListener);
+
+        eventExecutor.shutdown();
+
+        log.info("Stopped");
+    }
+
+    private class InternalKubernetesExternalLbConfigListener
+            implements KubernetesExternalLbConfigListener {
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+        @Override
+        public void event(KubernetesExternalLbConfigEvent event) {
+            switch (event.type()) {
+                case KUBERNETES_EXTERNAL_LB_CONFIG_CREATED:
+                case KUBERNETES_EXTERNAL_LB_CONFIG_UPDATED:
+                    eventExecutor.execute(() -> processConfigCreatedOrUpdated(event.subject()));
+                case KUBERNETES_EXTERNAL_LB_CONFIG_REMOVED:
+                default:
+                    //do nothing
+                    break;
+            }
+        }
+        private void processConfigCreatedOrUpdated(KubernetesExternalLbConfig config) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+
+            if (config.loadBalancerGwMac() != null) {
+                return;
+            }
+
+            processKubernetesExternalLbConfigMacLearning(config);
+        }
+    }
+
+    private class InternalNodeEventListener implements KubevirtNodeListener {
+
+        private boolean isRelevantHelper() {
+            return Objects.equals(localNodeId, leadershipService.getLeader(appId.name()));
+        }
+
+
+        @Override
+        public void event(KubevirtNodeEvent event) {
+            switch (event.type()) {
+                case KUBEVIRT_NODE_COMPLETE:
+                    eventExecutor.execute(() -> processNodeCompletion(event.subject()));
+                    break;
+                case KUBEVIRT_NODE_INCOMPLETE:
+                case KUBEVIRT_NODE_UPDATED:
+                case KUBEVIRT_NODE_REMOVED:
+                    break;
+
+                default:
+                    // do nothing
+                    break;
+            }
+        }
+
+        private void processNodeCompletion(KubevirtNode node) {
+            if (!isRelevantHelper()) {
+                return;
+            }
+            if (node.type().equals(GATEWAY)) {
+                KubernetesExternalLbInterface externalLbInterface =
+                        node.kubernetesExternalLbInterface();
+
+                if (externalLbInterface != null && externalLbInterface.externalLbGwMac() == null) {
+                    processKubernetesExternalLbIntfGwMacLearningForGatewayNode(node);
+                }
+
+                KubernetesExternalLbConfig config =
+                        externalLbConfigAdminService.lbConfigs().stream().findAny().orElse(null);
+
+                if (config != null && config.loadBalancerGwMac() == null) {
+                    processKubernetesExternalLbConfigMacLearning(config);
+                }
+            }
+        }
+    }
+
+    private void processKubernetesExternalLbConfigMacLearning(KubernetesExternalLbConfig config) {
+        nodeAdminService.completeExternalLbGatewayNodes().forEach(gateway -> {
+            setRuleArpRequestToController(config.loadBalancerGwIp(),
+                    KUBERNETES_EXTERNAL_LB_FAKE_MAC, gateway, true);
+        });
+
+        KubevirtNode gateway = nodeAdminService.completeExternalLbGatewayNodes()
+                .stream().findAny().orElse(null);
+        if (gateway == null) {
+            return;
+        }
+        PortNumber externalPatchPortNum = KubevirtNetworkingUtil.externalPatchPortNum(deviceService, gateway);
+
+        if (externalPatchPortNum == null) {
+            log.warn("processKubernetesExternalLbConfigMacLearning" +
+                            " called but there's no external patchPort for {}. Stop this task.",
+                    gateway);
+            return;
+        }
+
+        retrievePeerMac(NON_ROUTABLE_META_ADDRESS, KUBERNETES_EXTERNAL_LB_FAKE_MAC,
+                config.loadBalancerGwIp(), gateway, externalPatchPortNum);
+        checkKubernetesExternalLbConfigMacRetrieved(config, gateway);
+    }
+
+
+    private void processKubernetesExternalLbIntfGwMacLearningForGatewayNode(KubevirtNode gatewayNode) {
+
+        MacAddress elbIntfMac = kubernetesElbMac(deviceService, gatewayNode);
+        if (elbIntfMac == null) {
+            log.warn("processKubernetesExternalLbGwMacLearningForNode called but elbIntfMac is null. Stop this task.");
+            return;
+        }
+
+        KubernetesExternalLbInterface externalLbInterface = gatewayNode.kubernetesExternalLbInterface();
+
+        setRuleArpRequestToController(externalLbInterface.externalLbGwIp(), elbIntfMac,
+                gatewayNode, true);
+
+        PortNumber elbPatchPortNum = KubevirtNetworkingUtil.elbPatchPortNum(deviceService, gatewayNode);
+
+        if (elbPatchPortNum == null) {
+            log.warn("processKubernetesExternalLbIntfGwMacLearningForGatewayNode" +
+                            " called but there's no elb patchPort for {}. Stop this task.",
+                    gatewayNode);
+            return;
+        }
+
+        retrievePeerMac(externalLbInterface.externalLbIp(), elbIntfMac,
+                externalLbInterface.externalLbGwIp(), gatewayNode, elbPatchPortNum);
+
+        checkKubernetesExternalLbIntfGwMacRetrieved(gatewayNode);
+    }
+
+    private void checkKubernetesExternalLbIntfGwMacRetrieved(KubevirtNode gateway) {
+        KubernetesExternalLbInterface externalLbInterface = gateway.kubernetesExternalLbInterface();
+
+        MacAddress elbIntfMac = kubernetesElbMac(deviceService, gateway);
+        if (elbIntfMac == null) {
+            log.warn("setDownstreamRules called but elbIntfMac is null. Stop this task.");
+            return;
+        }
+
+        KubernetexExternalLbIntfTimerTask task = new KubernetexExternalLbIntfTimerTask(
+                externalLbInterface.externalLbIp(), elbIntfMac,
+                externalLbInterface.externalLbGwIp(), gateway);
+
+        externalLbIntfGwTimer.schedule(task, INITIAL_DELAY, TASK_PERIOD);
+    }
+
+    private void checkKubernetesExternalLbConfigMacRetrieved(KubernetesExternalLbConfig config,
+                                                             KubevirtNode gateway) {
+        KubernetesExternalLbConfigTimerTask task = new KubernetesExternalLbConfigTimerTask(
+                IpAddress.valueOf("0.0.0.0"), KUBERNETES_EXTERNAL_LB_FAKE_MAC,
+                config.loadBalancerGwIp(), gateway);
+
+        externalLbGwTimer.schedule(task, INITIAL_DELAY, TASK_PERIOD);
+    }
+
+
+    private void setRuleArpRequestToController(IpAddress targetIpAddress,
+                                               MacAddress dstMac,
+                                               KubevirtNode gatewayNode,
+                                               boolean install) {
+        if (targetIpAddress == null || dstMac == null || gatewayNode == null) {
+            return;
+        }
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+                .matchArpOp(ARP.OP_REPLY)
+                .matchArpSpa(targetIpAddress.getIp4Address())
+                .matchArpTha(dstMac)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .punt()
+                .build();
+
+        kubevirtFlowRuleService.setRule(
+                appId,
+                gatewayNode.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_ARP_GATEWAY_RULE,
+                GW_ENTRY_TABLE,
+                install
+        );
+    }
+
+
+    private void retrievePeerMac(IpAddress srcIp, MacAddress srcMac,
+                                 IpAddress peerIp, KubevirtNode gatewayNode,
+                                 PortNumber portNumber) {
+        log.trace("Sending ARP request to the peer {} to retrieve the MAC address.",
+                peerIp.getIp4Address().toString());
+
+        Ethernet ethRequest = ARP.buildArpRequest(srcMac.toBytes(),
+                srcIp.toOctets(),
+                peerIp.toOctets(), VlanId.NO_VID);
+
+        if (gatewayNode == null) {
+            log.warn("retrievePeerMac called but there's no gateway node for {}. Stop this task.",
+                    gatewayNode);
+            return;
+        }
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(portNumber)
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(
+                gatewayNode.intgBridge(),
+                treatment,
+                ByteBuffer.wrap(ethRequest.serialize())));
+
+    }
+
+    private class KubernetesExternalLbConfigTimerTask extends TimerTask {
+        private final IpAddress srcIp;
+        private final MacAddress srcMac;
+        private final IpAddress peerIp;
+        private final KubevirtNode gatewayNode;
+
+        public KubernetesExternalLbConfigTimerTask(IpAddress srcIp, MacAddress srcMac,
+                                 IpAddress peerIp, KubevirtNode gatewayNode) {
+            this.srcIp = srcIp;
+            this.srcMac = srcMac;
+            this.peerIp = peerIp;
+            this.gatewayNode = gatewayNode;
+        }
+
+        @Override
+        public void run() {
+            KubernetesExternalLbConfig config =
+                    externalLbConfigAdminService.lbConfigs().stream().findAny().orElse(null);
+
+            if (config == null) {
+                return;
+            }
+
+            if (config.loadBalancerGwMac() != null) {
+                log.info("Peer Mac {} for KubernetesExternalLbGateway is retrieved. Stop this task.",
+                        config.loadBalancerGwMac());
+                this.cancel();
+                return;
+            }
+
+            PortNumber externalPatchPortNum = KubevirtNetworkingUtil.externalPatchPortNum(deviceService, gatewayNode);
+
+            if (externalPatchPortNum == null) {
+                log.warn("processKubernetesExternalLbConfigMacLearning" +
+                                " called but there's no external patchPort for {}. Stop this task.",
+                        gatewayNode);
+                return;
+            }
+
+            retrievePeerMac(srcIp, srcMac, peerIp, gatewayNode, externalPatchPortNum);
+        }
+    }
+
+
+    private class KubernetexExternalLbIntfTimerTask extends TimerTask {
+        private final IpAddress srcIp;
+        private final MacAddress srcMac;
+        private final IpAddress peerIp;
+        private final KubevirtNode gatewayNode;
+
+        public KubernetexExternalLbIntfTimerTask(IpAddress srcIp, MacAddress srcMac,
+                                                 IpAddress peerIp, KubevirtNode gatewayNode) {
+            this.srcIp = srcIp;
+            this.srcMac = srcMac;
+            this.peerIp = peerIp;
+            this.gatewayNode = gatewayNode;
+        }
+
+        @Override
+        public void run() {
+
+            KubernetesExternalLbInterface externalLbInterface = gatewayNode.kubernetesExternalLbInterface();
+
+            if (externalLbInterface.externalLbGwMac() != null) {
+                log.info("Peer Mac {} for KubernetesExternalLbIntfGw for node {} is retrieved. Stop this task.",
+                        externalLbInterface.externalLbGwMac(), gatewayNode.hostname());
+                this.cancel();
+                return;
+            }
+
+            PortNumber elbPatchPortNum = KubevirtNetworkingUtil.elbPatchPortNum(deviceService, gatewayNode);
+
+            if (elbPatchPortNum == null) {
+                log.warn("processKubernetesExternalLbIntfGwMacLearningForGatewayNode" +
+                                " called but there's no elb patchPort for {}. Stop this task.",
+                        gatewayNode);
+                return;
+            }
+
+            retrievePeerMac(srcIp, srcMac, peerIp, gatewayNode, elbPatchPortNum);
+        }
+    }
+
+    private class InternalPacketProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+            if (context.isHandled()) {
+                return;
+            }
+
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethernet = pkt.parsed();
+
+            if (ethernet != null && ethernet.getEtherType() == Ethernet.TYPE_ARP) {
+                processArpPacket(ethernet);
+            }
+        }
+
+        private void processArpPacket(Ethernet ethernet) {
+            ARP arp = (ARP) ethernet.getPayload();
+
+            if (arp.getOpCode() == ARP.OP_REQUEST) {
+                return;
+            }
+            log.trace("ARP request {}", arp);
+
+            KubernetesExternalLbConfig config =
+                    externalLbConfigAdminService.lbConfigs().stream().findAny().orElse(null);
+
+            IpAddress spa = Ip4Address.valueOf(arp.getSenderProtocolAddress());
+            MacAddress sha = MacAddress.valueOf(arp.getSenderHardwareAddress());
+
+            if (config != null && config.loadBalancerGwIp().equals(spa)) {
+                externalLbConfigAdminService.updateKubernetesExternalLbConfig(config.updateLbGatewayMac(sha));
+            }
+
+            nodeAdminService.completeExternalLbGatewayNodes().forEach(gateway -> {
+                KubernetesExternalLbInterface externalLbInterface =
+                        gateway.kubernetesExternalLbInterface();
+                if (externalLbInterface == null) {
+                    return;
+                }
+
+                if (externalLbInterface.externalLbGwIp().equals(spa)) {
+                    if (externalLbInterface.externalLbGwMac() == null ||
+                            !externalLbInterface.externalLbGwMac().equals(sha)) {
+                        nodeAdminService.updateNode(gateway.updateKubernetesElbIntfGwMac(sha));
+                    }
+                }
+            });
+        }
+    }
+}
+
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbHandler.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbHandler.java
index 7b1c733..5953c8a 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbHandler.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesExternalLbHandler.java
@@ -80,7 +80,7 @@
 public class KubernetesExternalLbHandler {
     protected final Logger log = getLogger(getClass());
 
-    private static final int TP_PORT_MINIMUM_NUM = 1025;
+    private static final int TP_PORT_MINIMUM_NUM = 10000;
     private static final int TP_PORT_MAXIMUM_NUM = 65535;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
@@ -182,9 +182,9 @@
                 return;
             }
 
-            if (lb.electedGateway() == null || lb.electedWorker() == null) {
+            if (lb.electedGateway() == null || lb.electedWorker() == null || lb.loadBalancerGwMac() == null) {
                 log.warn("processKubernetesExternalLbCreatedOrUpdated called but electedGateway " +
-                        "or electedWorker is null. Stop this task.");
+                        "or electedWorker or loadBalancerGwMacis null. Stop this task.");
                 return;
             }
 
@@ -198,7 +198,9 @@
                 return;
             }
 
-            if (lb.electedWorker() == null || oldGatway == null) {
+            if (lb.electedWorker() == null || oldGatway == null || lb.loadBalancerGwMac() == null) {
+                log.warn("processKubernetesExternalLbGatewayChanged called but old electedWorker " +
+                        "or electedWorker or loadBalancerGwMacis null. Stop this task.");
                 return;
             }
 
@@ -326,13 +328,13 @@
             return;
         }
 
-        lb.nodePortSet().forEach(nodeport -> {
+        lb.servicePorts().forEach(servicePort -> {
             TrafficSelector selector = DefaultTrafficSelector.builder()
                     .matchEthType(Ethernet.TYPE_IPV4)
                     .matchEthDst(KUBERNETES_EXTERNAL_LB_FAKE_MAC)
                     .matchIPDst(loadBalancerIp.toIpPrefix())
                     .matchIPProtocol(IPv4.PROTOCOL_TCP)
-                    .matchTcpDst(TpPort.tpPort(nodeport.intValue()))
+                    .matchTcpDst(TpPort.tpPort(servicePort.port().intValue()))
                     .build();
 
             ExtensionTreatment natTreatment = RulePopulatorUtil
@@ -351,6 +353,7 @@
                     .setEthSrc(elbIntfMac)
                     .setEthDst(externalLbInterface.externalLbGwMac())
                     .setIpDst(electedWorker.dataIp())
+                    .setTcpDst(TpPort.tpPort(servicePort.nodePort().intValue()))
                     .setOutput(elbBridgePortNum)
                     .build();
 
@@ -404,13 +407,13 @@
             return;
         }
 
-        lb.nodePortSet().forEach(nodePort -> {
+        lb.servicePorts().forEach(servicePort -> {
             TrafficSelector.Builder sBuilder = DefaultTrafficSelector.builder()
                     .matchEthType(Ethernet.TYPE_IPV4)
                     .matchIPSrc(electedWorker.dataIp().toIpPrefix())
                     .matchIPDst(externalLbInterface.externalLbIp().toIpPrefix())
                     .matchIPProtocol(IPv4.PROTOCOL_TCP)
-                    .matchTcpSrc(TpPort.tpPort(nodePort.intValue()));
+                    .matchTcpSrc(TpPort.tpPort(servicePort.nodePort().intValue()));
 
             ExtensionTreatment natTreatment = RulePopulatorUtil
                     .niciraConnTrackTreatmentBuilder(driverService, gateway.intgBridge())
@@ -423,6 +426,7 @@
                     .setEthSrc(KUBERNETES_EXTERNAL_LB_FAKE_MAC)
                     .setIpSrc(lb.loadBalancerIp())
                     .setEthDst(lb.loadBalancerGwMac())
+                    .setTcpSrc(TpPort.tpPort(servicePort.port().intValue()))
                     .extension(natTreatment, gateway.intgBridge())
                     .transition(GW_DROP_TABLE);
 
@@ -438,13 +442,12 @@
             sBuilder = DefaultTrafficSelector.builder()
                     .matchEthType(Ethernet.TYPE_IPV4)
                     .matchIPProtocol(IPv4.PROTOCOL_TCP)
-                    .matchTcpSrc(TpPort.tpPort(nodePort.intValue()));
-
+                    .matchEthSrc(KUBERNETES_EXTERNAL_LB_FAKE_MAC)
+                    .matchIPSrc(lb.loadBalancerIp().toIpPrefix())
+                    .matchEthDst(lb.loadBalancerGwMac())
+                    .matchTcpSrc(TpPort.tpPort(servicePort.port().intValue()));
 
             tBuilder = DefaultTrafficTreatment.builder()
-                    .setEthSrc(KUBERNETES_EXTERNAL_LB_FAKE_MAC)
-                    .setIpSrc(lb.loadBalancerIp())
-                    .setEthDst(lb.loadBalancerGwMac())
                     .setOutput(externalPatchPortNum);
 
             flowService.setRule(
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesServiceWatcher.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesServiceWatcher.java
index 2aec742..d80ee25 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesServiceWatcher.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/impl/KubernetesServiceWatcher.java
@@ -34,8 +34,10 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.kubevirtnetworking.api.DefaultKubernetesExternalLb;
+import org.onosproject.kubevirtnetworking.api.DefaultKubernetesServicePort;
 import org.onosproject.kubevirtnetworking.api.KubernetesExternalLb;
 import org.onosproject.kubevirtnetworking.api.KubernetesExternalLbAdminService;
+import org.onosproject.kubevirtnetworking.api.KubernetesServicePort;
 import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfig;
 import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent;
 import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigListener;
@@ -63,6 +65,7 @@
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.kubevirtnetworking.api.Constants.KUBEVIRT_NETWORKING_APP_ID;
+import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.configMapUpdated;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.gatewayNodeForSpecifiedService;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.k8sClient;
 import static org.onosproject.kubevirtnetworking.util.KubevirtNetworkingUtil.workerNodeForSpecifiedService;
@@ -175,7 +178,7 @@
             switch (event.type()) {
                 case KUBERNETES_EXTERNAL_LB_CONFIG_CREATED:
                 case KUBERNETES_EXTERNAL_LB_CONFIG_UPDATED:
-                    eventExecutor.execute(this::processConfigUpdate);
+                    eventExecutor.execute(() -> processConfigUpdate(event.subject()));
                     break;
                 case KUBERNETES_EXTERNAL_LB_CONFIG_REMOVED:
                 default:
@@ -184,11 +187,13 @@
             }
         }
 
-        private void processConfigUpdate() {
+        private void processConfigUpdate(KubernetesExternalLbConfig externalLbConfig) {
             if (!isRelevantHelper()) {
                 return;
             }
-            addOrUpdateExternalLoadBalancers();
+            if (configMapUpdated(externalLbConfig)) {
+                addOrUpdateExternalLoadBalancers();
+            }
         }
     }
 
@@ -261,7 +266,9 @@
                 return;
             }
 
-            if (!configMapUpdated()) {
+            KubernetesExternalLbConfig config = lbConfigService.lbConfigs().stream().findAny().orElse(null);
+
+            if (!configMapUpdated(config)) {
                 log.warn("Config map is not set yet. Stop this task");
                 return;
             }
@@ -390,17 +397,6 @@
                 .findAny().isPresent();
     }
 
-    private boolean configMapUpdated() {
-        KubernetesExternalLbConfig config = lbConfigService.lbConfigs().stream().findAny().orElse(null);
-
-        if (config == null) {
-            return false;
-        }
-
-        return config.configName() != null && config.globalIpRange() != null &&
-                config.loadBalancerGwIp() != null && config.loadBalancerGwMac() != null;
-    }
-
     //Only process if the event when the service type is LoadBalancer
     private boolean isLoadBalancerType(Service service) {
         return service.getSpec().getType().equals(TYPE_LOADBALANCER);
@@ -421,13 +417,15 @@
             return null;
         }
 
-        Set<Integer> nodePortSet = Sets.newHashSet();
-        Set<Integer> portSet = Sets.newHashSet();
+        Set<KubernetesServicePort> servicePorts = Sets.newHashSet();
         Set<String> endpointSet = Sets.newHashSet();
 
         service.getSpec().getPorts().forEach(servicePort -> {
-            nodePortSet.add(servicePort.getNodePort());
-            portSet.add(servicePort.getPort());
+            if (servicePort.getPort() != null && servicePort.getNodePort() != null) {
+                servicePorts.add(DefaultKubernetesServicePort.builder()
+                        .nodePort(servicePort.getNodePort())
+                        .port(servicePort.getPort()).build());
+            }
         });
 
         nodeService.completeNodes(WORKER).forEach(workerNode -> {
@@ -452,8 +450,7 @@
 
         return DefaultKubernetesExternalLb.builder().serviceName(serviceName)
                 .loadBalancerIp(IpAddress.valueOf(lbIp))
-                .nodePortSet(nodePortSet)
-                .portSet(portSet)
+                .servicePorts(servicePorts)
                 .endpointSet(endpointSet)
                 .loadBalancerGwIp(IpAddress.valueOf(loadbalancerGatewayIp))
                 .loadBalancerGwMac(loadBalancerGatewayMac)
diff --git a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
index 539323f..345e670 100644
--- a/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
+++ b/apps/kubevirt-networking/app/src/main/java/org/onosproject/kubevirtnetworking/util/KubevirtNetworkingUtil.java
@@ -53,6 +53,7 @@
 import org.onosproject.kubevirtnetworking.api.KubevirtRouterService;
 import org.onosproject.kubevirtnode.api.DefaultKubevirtNode;
 import org.onosproject.kubevirtnode.api.DefaultKubevirtPhyInterface;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbConfig;
 import org.onosproject.kubevirtnode.api.KubernetesExternalLbInterface;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfig;
 import org.onosproject.kubevirtnode.api.KubevirtApiConfigService;
@@ -557,16 +558,32 @@
                                                               KubernetesExternalLb externalLb) {
         //TODO: enhance election logic for a better load balancing
 
-        int numOfGateways = nodeService.completeNodes(GATEWAY).size();
+        int numOfGateways = nodeService.completeExternalLbGatewayNodes().size();
         if (numOfGateways == 0) {
             return null;
         }
 
-        return (KubevirtNode) nodeService.completeNodes(GATEWAY)
+        return (KubevirtNode) nodeService.completeExternalLbGatewayNodes()
                 .toArray()[externalLb.hashCode() % numOfGateways];
     }
 
     /**
+     * Returns whether a mac address in kubernetes external lb config is updated.
+     *
+     * @param externalLbConfig kubernetes external lb config
+     * @return true if a mac address is added
+     */
+    public static boolean configMapUpdated(KubernetesExternalLbConfig externalLbConfig) {
+        if (externalLbConfig == null) {
+            return false;
+        }
+
+        return externalLbConfig.configName() != null && externalLbConfig.globalIpRange() != null &&
+                externalLbConfig.loadBalancerGwIp() != null && externalLbConfig.loadBalancerGwMac() != null;
+    }
+
+
+    /**
      * Returns the worker node for the specified kubernetes external lb.
      * Among worker nodes, only one worker would serve the traffic from and to the gateway.
      * Currently worker node is selected based on modulo operation with external lb hashcode.
diff --git a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbConfig.java b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbConfig.java
index 1147ac7..082c1be 100644
--- a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbConfig.java
+++ b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbConfig.java
@@ -103,7 +103,7 @@
         return DefaultKubernetesExternalLbConfig.builder()
                 .configName(configName)
                 .loadBalancerGwIp(loadBalancerGwIp)
-                .loadBalancerGwMac(loadBalancerGwMac)
+                .loadBalancerGwMac(gatewayMac)
                 .globalIpRange(globalIpRange)
                 .build();
     }
diff --git a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbInterface.java b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbInterface.java
index fe53b53..676191e 100644
--- a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbInterface.java
+++ b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubernetesExternalLbInterface.java
@@ -109,7 +109,6 @@
             checkArgument(elbBridgeName != null, NOT_NULL_MSG, "externalLbBridgeName");
             checkArgument(elbIp != null, NOT_NULL_MSG, "externalLbIp");
             checkArgument(elbGwIp != null, NOT_NULL_MSG, "externalLbGwIp");
-            checkArgument(elbGwMac != null, NOT_NULL_MSG, "externalLbGwMac");
 
             return new DefaultKubernetesExternalLbInterface(elbBridgeName, elbIp, elbGwIp, elbGwMac);
         }
diff --git a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubevirtNode.java b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubevirtNode.java
index 2c3d0be..cc5e43a 100644
--- a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubevirtNode.java
+++ b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/DefaultKubevirtNode.java
@@ -193,6 +193,31 @@
     }
 
     @Override
+    public KubevirtNode updateKubernetesElbIntfGwMac(MacAddress macAddress) {
+
+        KubernetesExternalLbInterface externalLbInterface = DefaultKubernetesExternalLbInterface.builder()
+                .externalLbIp(this.kubernetesExternalLbIntf.externalLbIp())
+                .externalLbBridgeName(this.kubernetesExternalLbIntf.externalLbBridgeName())
+                .externallbGwIp(this.kubernetesExternalLbIntf.externalLbGwIp())
+                .externalLbGwMac(macAddress)
+                .build();
+
+        return new Builder()
+                .hostname(hostname)
+                .clusterName(clusterName)
+                .type(type)
+                .intgBridge(intgBridge)
+                .tunBridge(tunBridge)
+                .managementIp(managementIp)
+                .dataIp(dataIp)
+                .state(state)
+                .phyIntfs(phyIntfs)
+                .gatewayBridgeName(gatewayBridgeName)
+                .kubernetesExternalLbInterface(externalLbInterface)
+                .build();
+    }
+
+    @Override
     public Collection<KubevirtPhyInterface> phyIntfs() {
         if (phyIntfs == null) {
             return new ArrayList<>();
@@ -366,10 +391,6 @@
         private KubevirtNodeState state;
         private Collection<KubevirtPhyInterface> phyIntfs;
         private String gatewayBridgeName;
-        private String elbBridgeName;
-        private IpAddress elbIp;
-        private IpAddress elbGwIp;
-        private MacAddress elbGwMac;
         private KubernetesExternalLbInterface kubernetesExternalLbInterface;
 
         // private constructor not intended to use from external
diff --git a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNode.java b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNode.java
index 502784d..4e02997 100644
--- a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNode.java
+++ b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNode.java
@@ -16,6 +16,7 @@
 package org.onosproject.kubevirtnode.api;
 
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 
@@ -140,6 +141,13 @@
     KubevirtNode updateTunBridge(DeviceId deviceId);
 
     /**
+     * Returns new kubevirt node instance with given kubernetes external lb intf gw mac.
+     * @param macAddress kubernetes external lb intf gw mac
+     * @return updated kubevirt node
+     */
+    KubevirtNode updateKubernetesElbIntfGwMac(MacAddress macAddress);
+
+    /**
      * Returns a collection of physical interfaces.
      *
      * @return physical interfaces
diff --git a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNodeService.java b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNodeService.java
index e9ed65b..118c97b 100644
--- a/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNodeService.java
+++ b/apps/kubevirt-node/api/src/main/java/org/onosproject/kubevirtnode/api/KubevirtNodeService.java
@@ -59,6 +59,12 @@
     Set<KubevirtNode> completeNodes(KubevirtNode.Type type);
 
     /**
+     * Returns all gateway nodes with complete state and external lb interface added.
+     * @return set of kubevirt nodes
+     */
+    Set<KubevirtNode> completeExternalLbGatewayNodes();
+
+    /**
      * Returns the node with the specified hostname.
      *
      * @param hostname hostname
diff --git a/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DistributedKubernetesExternalLbConfigStore.java b/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DistributedKubernetesExternalLbConfigStore.java
index 16a816e..bee1d7b 100644
--- a/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DistributedKubernetesExternalLbConfigStore.java
+++ b/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/DistributedKubernetesExternalLbConfigStore.java
@@ -48,12 +48,9 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
-import static org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent.Type
-        .KUBERNETES_EXTERNAL_LB_CONFIG_CREATED;
-import static org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent.Type
-        .KUBERNETES_EXTERNAL_LB_CONFIG_REMOVED;
-import static org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent.Type
-        .KUBERNETES_EXTERNAL_LB_CONFIG_UPDATED;
+import static org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent.Type.KUBERNETES_EXTERNAL_LB_CONFIG_CREATED;
+import static org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent.Type.KUBERNETES_EXTERNAL_LB_CONFIG_REMOVED;
+import static org.onosproject.kubevirtnode.api.KubernetesExternalLbConfigEvent.Type.KUBERNETES_EXTERNAL_LB_CONFIG_UPDATED;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -128,7 +125,14 @@
         lbConfigStore.compute(lbConfig.configName(), (configName, existing) -> {
             final String error = lbConfig.configName() + ERR_NOT_FOUND;
             checkArgument(existing != null, error);
-            return lbConfig;
+
+            if (lbConfig.equals(existing) && lbConfig.loadBalancerGwMac() == null &&
+                    existing.loadBalancerGwMac() != null) {
+                return existing;
+            } else {
+                return lbConfig;
+            }
+
         });
     }
 
diff --git a/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/KubevirtNodeManager.java b/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/KubevirtNodeManager.java
index c2b9aad..4a148c8 100644
--- a/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/KubevirtNodeManager.java
+++ b/apps/kubevirt-node/app/src/main/java/org/onosproject/kubevirtnode/impl/KubevirtNodeManager.java
@@ -24,6 +24,7 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.event.ListenerRegistry;
+import org.onosproject.kubevirtnode.api.KubernetesExternalLbInterface;
 import org.onosproject.kubevirtnode.api.KubevirtNode;
 import org.onosproject.kubevirtnode.api.KubevirtNodeAdminService;
 import org.onosproject.kubevirtnode.api.KubevirtNodeEvent;
@@ -59,6 +60,7 @@
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.kubevirtnode.api.Constants.INTEGRATION_BRIDGE;
 import static org.onosproject.kubevirtnode.api.Constants.TUNNEL_BRIDGE;
+import static org.onosproject.kubevirtnode.api.KubevirtNode.Type.GATEWAY;
 import static org.onosproject.kubevirtnode.impl.OsgiPropertyConstants.OVSDB_PORT;
 import static org.onosproject.kubevirtnode.impl.OsgiPropertyConstants.OVSDB_PORT_NUM_DEFAULT;
 import static org.onosproject.kubevirtnode.util.KubevirtNodeUtil.genDpidFromName;
@@ -275,6 +277,23 @@
     }
 
     @Override
+    public Set<KubevirtNode> completeExternalLbGatewayNodes() {
+        Set<KubevirtNode> nodes = nodeStore.nodes().stream()
+                .filter(node -> node.type() == GATEWAY &&
+                        node.state() == KubevirtNodeState.COMPLETE)
+                .filter(node -> {
+                    KubernetesExternalLbInterface externalLbInterface = node.kubernetesExternalLbInterface();
+
+                    if (externalLbInterface != null) {
+                        return true;
+                    }
+                    return false;
+                })
+                .collect(Collectors.toSet());
+        return ImmutableSet.copyOf(nodes);
+    }
+
+    @Override
     public KubevirtNode node(String hostname) {
         return nodeStore.node(hostname);
     }