Listen on k8s port event to create instance port and host

Change-Id: I59ab1f5f422ce2ba8f5432279ceec6d77cd96f06
diff --git a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHandler.java b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHandler.java
index 0165975..d62a923 100644
--- a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHandler.java
+++ b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHandler.java
@@ -66,8 +66,12 @@
 import static org.onosproject.k8snetworking.api.Constants.PRIORITY_TUNNEL_TAG_RULE;
 import static org.onosproject.k8snetworking.api.Constants.TUN_ENTRY_TABLE;
 import static org.onosproject.k8snetworking.api.Constants.VTAG_TABLE;
+import static org.onosproject.k8snetworking.api.K8sNetwork.Type.GENEVE;
+import static org.onosproject.k8snetworking.api.K8sNetwork.Type.GRE;
+import static org.onosproject.k8snetworking.api.K8sNetwork.Type.VXLAN;
 import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.getPropertyValue;
 import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.tunnelPortNumByNetId;
+import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.tunnelPortNumByNetType;
 import static org.onosproject.k8snetworking.util.RulePopulatorUtil.buildExtension;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -228,9 +232,9 @@
     }
 
     private void setRulesForTunnelBridge(K8sNode node, boolean install) {
-        setRulesForTunnelBridgeByType(node, K8sNetwork.Type.VXLAN, install);
-        setRulesForTunnelBridgeByType(node, K8sNetwork.Type.GRE, install);
-        setRulesForTunnelBridgeByType(node, K8sNetwork.Type.GENEVE, install);
+        setRulesForTunnelBridgeByType(node, VXLAN, install);
+        setRulesForTunnelBridgeByType(node, GRE, install);
+        setRulesForTunnelBridgeByType(node, GENEVE, install);
     }
 
     private void setRulesForTunnelBridgeByType(K8sNode node, K8sNetwork.Type type, boolean install) {
@@ -239,13 +243,13 @@
 
         switch (type) {
             case VXLAN:
-                portNum = node.vxlanPortNum();
+                portNum = tunnelPortNumByNetType(VXLAN, node);
                 break;
             case GRE:
-                portNum = node.grePortNum();
+                portNum = tunnelPortNumByNetType(GRE, node);
                 break;
             case GENEVE:
-                portNum = node.genevePortNum();
+                portNum = tunnelPortNumByNetType(GENEVE, node);
                 break;
             default:
                 return;
diff --git a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHostProvider.java b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHostProvider.java
index 7510aaf..167d4ca 100644
--- a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHostProvider.java
+++ b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/impl/K8sSwitchingHostProvider.java
@@ -23,6 +23,8 @@
 import org.onosproject.core.CoreService;
 import org.onosproject.k8snetworking.api.K8sNetwork;
 import org.onosproject.k8snetworking.api.K8sNetworkAdminService;
+import org.onosproject.k8snetworking.api.K8sNetworkEvent;
+import org.onosproject.k8snetworking.api.K8sNetworkListener;
 import org.onosproject.k8snetworking.api.K8sPort;
 import org.onosproject.k8snode.api.K8sNode;
 import org.onosproject.k8snode.api.K8sNodeEvent;
@@ -65,8 +67,11 @@
 import static org.onosproject.k8snetworking.api.Constants.ANNOTATION_NETWORK_ID;
 import static org.onosproject.k8snetworking.api.Constants.ANNOTATION_PORT_ID;
 import static org.onosproject.k8snetworking.api.Constants.ANNOTATION_SEGMENT_ID;
+import static org.onosproject.k8snetworking.api.Constants.GENEVE;
+import static org.onosproject.k8snetworking.api.Constants.GRE;
 import static org.onosproject.k8snetworking.api.Constants.K8S_NETWORKING_APP_ID;
-import static org.onosproject.k8snetworking.api.Constants.PORT_NAME_PREFIX_CONTAINER;
+import static org.onosproject.k8snetworking.api.Constants.VXLAN;
+import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.existingContainerPort;
 import static org.onosproject.k8snetworking.util.K8sNetworkingUtil.isContainer;
 import static org.onosproject.k8snode.api.K8sNodeState.INIT;
 import static org.onosproject.net.AnnotationKeys.PORT_NAME;
@@ -81,7 +86,6 @@
 
     private static final String ERR_ADD_HOST = "Failed to add host: ";
     private static final String SONA_HOST_SCHEME = "sona-k8s";
-    private static final int PORT_PREFIX_LENGTH = 4;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY)
     protected CoreService coreService;
@@ -112,6 +116,8 @@
             new InternalDeviceListener();
     private final InternalK8sNodeListener internalK8sNodeListener =
             new InternalK8sNodeListener();
+    private final InternalK8sNetworkListener internalK8sNetworkListener =
+            new InternalK8sNetworkListener();
 
     /**
      * Creates kubernetes switching host provider.
@@ -125,6 +131,7 @@
         coreService.registerApplication(K8S_NETWORKING_APP_ID);
         deviceService.addListener(internalDeviceListener);
         k8sNodeService.addListener(internalK8sNodeListener);
+        k8sNetworkService.addListener(internalK8sNetworkListener);
         hostProviderService = hostProviderRegistry.register(this);
 
         log.info("Started");
@@ -133,6 +140,7 @@
     @Deactivate
     protected void deactivate() {
         hostProviderRegistry.unregister(this);
+        k8sNetworkService.removeListener(internalK8sNetworkListener);
         k8sNodeService.removeListener(internalK8sNodeListener);
         deviceService.removeListener(internalDeviceListener);
 
@@ -272,7 +280,7 @@
 
         if (isContainer(portName)) {
             return k8sNetworkService.ports().stream()
-                    .filter(p -> p.portId().contains(portName.substring(PORT_PREFIX_LENGTH)))
+                    .filter(p -> existingContainerPort(p.portId(), portName))
                     .findAny().orElse(null);
         } else {
             return null;
@@ -290,8 +298,7 @@
 
             String portName = port.annotations().value(PORT_NAME);
 
-            return !Strings.isNullOrEmpty(portName) &&
-                    portName.startsWith(PORT_NAME_PREFIX_CONTAINER);
+            return !Strings.isNullOrEmpty(portName) && isContainer(portName);
         }
 
         private boolean isRelevantHelper(DeviceEvent event) {
@@ -435,4 +442,46 @@
                     });
         }
     }
+
+    private class InternalK8sNetworkListener implements K8sNetworkListener {
+
+        @Override
+        public void event(K8sNetworkEvent event) {
+            switch (event.type()) {
+                case K8S_PORT_CREATED:
+                    executor.execute(() -> processK8sPortAddition(event));
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        private void processK8sPortAddition(K8sNetworkEvent event) {
+            String portId = event.port().portId();
+            for (Device device : deviceService.getDevices()) {
+                Port port = deviceService.getPorts(device.id()).stream()
+                        .filter(Port::isEnabled)
+                        .filter(p -> p.annotations().value(PORT_NAME) != null)
+                        .filter(p -> existingContainerPort(portId, p.annotations().value(PORT_NAME)))
+                        .findAny().orElse(null);
+
+                if (port != null) {
+                    String upperPortName = port.annotations().value(PORT_NAME).toUpperCase();
+                    // we do not handle tunnel typed port
+                    if (upperPortName.contains(VXLAN) || upperPortName.contains(GRE) ||
+                            upperPortName.contains(GENEVE)) {
+                        continue;
+                    }
+
+                    // if we have null device ID, we simply update the device ID on the k8s port
+                    if (event.port().deviceId() == null) {
+                        K8sPort updated = event.port().updateDeviceId(device.id());
+                        k8sNetworkService.updatePort(updated);
+                    }
+
+                    processPortAdded(port);
+                }
+            }
+        }
+    }
 }
diff --git a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/util/K8sNetworkingUtil.java b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/util/K8sNetworkingUtil.java
index 17240fb..e63a685 100644
--- a/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/util/K8sNetworkingUtil.java
+++ b/apps/k8s-networking/app/src/main/java/org/onosproject/k8snetworking/util/K8sNetworkingUtil.java
@@ -29,6 +29,7 @@
 import io.fabric8.kubernetes.client.KubernetesClient;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.net.util.SubnetUtils;
+import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
@@ -43,10 +44,15 @@
 import org.onosproject.k8snetworking.api.K8sServiceService;
 import org.onosproject.k8snode.api.K8sApiConfig;
 import org.onosproject.k8snode.api.K8sApiConfigService;
+import org.onosproject.k8snode.api.K8sHost;
+import org.onosproject.k8snode.api.K8sHostService;
 import org.onosproject.k8snode.api.K8sNode;
 import org.onosproject.k8snode.api.K8sNodeService;
+import org.onosproject.k8snode.api.K8sTunnelBridge;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.group.DefaultGroupKey;
 import org.onosproject.net.group.GroupKey;
 import org.slf4j.Logger;
@@ -63,8 +69,13 @@
 import java.util.stream.Collectors;
 
 import static org.onosproject.k8snetworking.api.Constants.DEFAULT_NAMESPACE_HASH;
-import static org.onosproject.k8snetworking.api.Constants.PORT_NAME_PREFIX_CONTAINER;
+import static org.onosproject.k8snetworking.api.Constants.NORMAL_PORT_NAME_PREFIX_CONTAINER;
+import static org.onosproject.k8snetworking.api.Constants.NORMAL_PORT_PREFIX_LENGTH;
+import static org.onosproject.k8snetworking.api.Constants.PT_PORT_NAME_PREFIX_CONTAINER;
+import static org.onosproject.k8snetworking.api.Constants.PT_PORT_PREFIX_LENGTH;
 import static org.onosproject.k8snetworking.api.K8sPort.State.INACTIVE;
+import static org.onosproject.k8snode.api.K8sApiConfig.Mode.PASSTHROUGH;
+import static org.onosproject.net.AnnotationKeys.PORT_NAME;
 
 /**
  * An utility that used in kubernetes networking app.
@@ -101,7 +112,31 @@
      * @return true if the port is associated with container; false otherwise
      */
     public static boolean isContainer(String portName) {
-        return portName != null && portName.contains(PORT_NAME_PREFIX_CONTAINER);
+        return portName != null && (portName.contains(NORMAL_PORT_NAME_PREFIX_CONTAINER) ||
+                portName.contains(PT_PORT_NAME_PREFIX_CONTAINER));
+    }
+
+    /**
+     * Checks that whether the compared ports exist in the source name.
+     *
+     * @param sourceName    source port name
+     * @param comparedName  port name to be compared
+     * @return true if the compared port name exists, false otherwise
+     */
+    public static boolean existingContainerPort(String sourceName, String comparedName) {
+        if (comparedName == null) {
+            return false;
+        }
+
+        if (comparedName.contains(NORMAL_PORT_NAME_PREFIX_CONTAINER)) {
+            return sourceName.contains(comparedName.substring(NORMAL_PORT_PREFIX_LENGTH));
+        }
+
+        if (comparedName.contains(PT_PORT_NAME_PREFIX_CONTAINER)) {
+            return sourceName.contains(comparedName.substring(PT_PORT_PREFIX_LENGTH));
+        }
+
+        return false;
     }
 
     /**
@@ -133,19 +168,58 @@
      */
     public static PortNumber tunnelPortNumByNetType(K8sNetwork.Type netType,
                                                     K8sNode node) {
-        switch (netType) {
-            case VXLAN:
-                return node.vxlanPortNum();
-            case GRE:
-                return node.grePortNum();
-            case GENEVE:
-                return node.genevePortNum();
-            default:
+        if (node.mode() == PASSTHROUGH) {
+            K8sHostService hostService =
+                    DefaultServiceDirectory.getService(K8sHostService.class);
+            Port port = null;
+            for (K8sHost host : hostService.hosts()) {
+                if (host.nodeNames().contains(node.hostname())) {
+                    for (K8sTunnelBridge bridge : host.tunBridges()) {
+                        if (bridge.tunnelId() == node.segmentId()) {
+                            String portName = netType.name().toLowerCase() +
+                                    "-" + node.segmentId();
+                            port = port(bridge.deviceId(), portName);
+                        }
+                    }
+                }
+            }
+
+            if (port == null) {
                 return null;
+            } else {
+                return port.number();
+            }
+
+        } else {
+            switch (netType) {
+                case VXLAN:
+                    return node.vxlanPortNum();
+                case GRE:
+                    return node.grePortNum();
+                case GENEVE:
+                    return node.genevePortNum();
+                default:
+                    return null;
+            }
         }
     }
 
     /**
+     * Obtains the port from the device with the given port name.
+     *
+     * @param deviceId      device identifier
+     * @param portName      port name
+     * @return port object
+     */
+    public static Port port(DeviceId deviceId, String portName) {
+        DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
+        return deviceService.getPorts(deviceId).stream()
+                .filter(p -> p.isEnabled() &&
+                        Objects.equals(p.annotations().value(PORT_NAME), portName))
+                .findAny().orElse(null);
+    }
+
+    /**
      * Obtains the property value with specified property key name.
      *
      * @param properties    a collection of properties