CORD-529, CORD-530 Allow VLAN and floating address pairs for PRIVATE network ports

And deprecated VSG network type

Change-Id: Ib0c1a2b1987463b3a6a4125e5aa7f4451eb51db8
diff --git a/src/main/java/org/opencord/cordvtn/api/core/Instance.java b/src/main/java/org/opencord/cordvtn/api/core/Instance.java
index 24c81b5..a86823f 100644
--- a/src/main/java/org/opencord/cordvtn/api/core/Instance.java
+++ b/src/main/java/org/opencord/cordvtn/api/core/Instance.java
@@ -36,9 +36,8 @@
     public static final String NETWORK_ID = "networkId";
     public static final String NETWORK_TYPE = "networkType";
     public static final String PORT_ID = "portId";
+    public static final String ORIGINAL_HOST_ID = "originalHostId";
     public static final String CREATE_TIME = "createTime";
-    public static final String NESTED_INSTANCE = "nestedInstance";
-    public static final String TRUE = "true";
 
     private final Host host;
 
@@ -107,12 +106,12 @@
     }
 
     /**
-     * Returns if the instance is nested container or not.
+     * Returns if the instance is original instance or additional one.
      *
-     * @return true if it's nested container; false otherwise
+     * @return true if it's additional instance; false otherwise
      */
-    public boolean isNestedInstance() {
-        return host.annotations().value(NESTED_INSTANCE) != null;
+    public boolean isAdditionalInstance() {
+        return host.annotations().value(ORIGINAL_HOST_ID) != null;
     }
 
     /**
diff --git a/src/main/java/org/opencord/cordvtn/api/core/InstanceService.java b/src/main/java/org/opencord/cordvtn/api/core/InstanceService.java
index c8eb5ef..596c9de 100644
--- a/src/main/java/org/opencord/cordvtn/api/core/InstanceService.java
+++ b/src/main/java/org/opencord/cordvtn/api/core/InstanceService.java
@@ -35,6 +35,14 @@
     void addInstance(ConnectPoint connectPoint);
 
     /**
+     * Adds a host with a given host ID and host description.
+     *
+     * @param hostId      host id
+     * @param description host description
+     */
+    void addInstance(HostId hostId, HostDescription description);
+
+    /**
      * Removes a service instance from a given connect point.
      *
      * @param connectPoint connect point
@@ -42,19 +50,9 @@
     void removeInstance(ConnectPoint connectPoint);
 
     /**
-     * Adds a nested instance with given host ID and host description.
-     * Nested instance can be a container inside a virtual machine, for example.
-     * DHCP is not supported for the nested instance.
-     *
-     * @param hostId host id
-     * @param description host description
-     */
-    void addNestedInstance(HostId hostId, HostDescription description);
-
-    /**
-     * Removes nested instance with a given host ID.
+     * Removes host with host ID.
      *
      * @param hostId host id
      */
-    void removeNestedInstance(HostId hostId);
+    void removeInstance(HostId hostId);
 }
diff --git a/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java b/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java
index 849a6ea..36cac9f 100644
--- a/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java
+++ b/src/main/java/org/opencord/cordvtn/api/net/ServiceNetwork.java
@@ -32,6 +32,7 @@
         PUBLIC,
         MANAGEMENT_HOST,
         MANAGEMENT_LOCAL,
+        @Deprecated
         VSG,
         ACCESS_AGENT
     }
diff --git a/src/main/java/org/opencord/cordvtn/impl/InstanceManager.java b/src/main/java/org/opencord/cordvtn/impl/InstanceManager.java
index 2011342..b31a575 100644
--- a/src/main/java/org/opencord/cordvtn/impl/InstanceManager.java
+++ b/src/main/java/org/opencord/cordvtn/impl/InstanceManager.java
@@ -197,20 +197,8 @@
     }
 
     @Override
-    public void addNestedInstance(HostId hostId, HostDescription description) {
-        DefaultAnnotations annotations  = DefaultAnnotations.builder()
-                .set(Instance.NESTED_INSTANCE, Instance.TRUE)
-                .build();
-        annotations = annotations.merge(annotations, description.annotations());
-
-        HostDescription nestedHost = new DefaultHostDescription(
-                description.hwAddress(),
-                description.vlan(),
-                description.location(),
-                description.ipAddress(),
-                annotations);
-
-        hostProvider.hostDetected(hostId, nestedHost, false);
+    public void addInstance(HostId hostId, HostDescription description) {
+        hostProvider.hostDetected(hostId, description, false);
     }
 
     @Override
@@ -222,7 +210,7 @@
     }
 
     @Override
-    public void removeNestedInstance(HostId hostId) {
+    public void removeInstance(HostId hostId) {
         hostProvider.hostVanished(hostId);
     }
 
diff --git a/src/main/java/org/opencord/cordvtn/impl/handler/AbstractInstanceHandler.java b/src/main/java/org/opencord/cordvtn/impl/handler/AbstractInstanceHandler.java
index 0471ee6..dc6a0d6 100644
--- a/src/main/java/org/opencord/cordvtn/impl/handler/AbstractInstanceHandler.java
+++ b/src/main/java/org/opencord/cordvtn/impl/handler/AbstractInstanceHandler.java
@@ -101,21 +101,21 @@
     }
 
     protected ServiceNetwork getServiceNetwork(Instance instance) {
-        ServiceNetwork vtnNet = snetService.serviceNetwork(instance.netId());
-        if (vtnNet == null) {
+        ServiceNetwork snet = snetService.serviceNetwork(instance.netId());
+        if (snet == null) {
             final String error = String.format(ERR_VTN_NETWORK, instance);
             throw new IllegalStateException(error);
         }
-        return vtnNet;
+        return snet;
     }
 
     protected ServicePort getServicePort(Instance instance) {
-        ServicePort vtnPort = snetService.servicePort(instance.portId());
-        if (vtnPort == null) {
+        ServicePort sport = snetService.servicePort(instance.portId());
+        if (sport == null) {
             final String error = String.format(ERR_VTN_PORT, instance);
             throw new IllegalStateException(error);
         }
-        return vtnPort;
+        return sport;
     }
 
     private class InternalHostListener implements HostListener {
diff --git a/src/main/java/org/opencord/cordvtn/impl/handler/DefaultInstanceHandler.java b/src/main/java/org/opencord/cordvtn/impl/handler/DefaultInstanceHandler.java
index 1684ce4..0edaaab 100644
--- a/src/main/java/org/opencord/cordvtn/impl/handler/DefaultInstanceHandler.java
+++ b/src/main/java/org/opencord/cordvtn/impl/handler/DefaultInstanceHandler.java
@@ -16,6 +16,7 @@
 package org.opencord.cordvtn.impl.handler;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
@@ -24,20 +25,43 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.HostId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultFlowRule;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
 import org.opencord.cordvtn.api.core.Instance;
 import org.opencord.cordvtn.api.core.InstanceHandler;
+import org.opencord.cordvtn.api.core.InstanceService;
+import org.opencord.cordvtn.api.net.AddressPair;
 import org.opencord.cordvtn.api.net.ServiceNetwork;
+import org.opencord.cordvtn.api.net.ServicePort;
 import org.opencord.cordvtn.api.node.CordVtnNode;
 import org.opencord.cordvtn.impl.CordVtnNodeManager;
 import org.opencord.cordvtn.impl.CordVtnPipeline;
 
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
 import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.*;
 
 /**
@@ -47,11 +71,17 @@
 public class DefaultInstanceHandler extends AbstractInstanceHandler implements InstanceHandler {
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CordVtnPipeline pipeline;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CordVtnNodeManager nodeManager;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected InstanceService instanceService;
+
     @Activate
     protected void activate() {
         netTypes = ImmutableSet.of(PRIVATE, PUBLIC, VSG);
@@ -65,24 +95,93 @@
 
     @Override
     public void instanceDetected(Instance instance) {
-        if (instance.isNestedInstance()) {
+        if (instance.isAdditionalInstance()) {
             return;
         }
-        log.info("Instance is detected {}", instance);
+        log.info("Instance is detected or updated {}", instance);
 
         ServiceNetwork snet = getServiceNetwork(instance);
         populateDefaultRules(instance, snet, true);
+
+        // TODO handle the case that vlan id is added and then removed
+        ServicePort sport = getServicePort(instance);
+        if (sport.vlanId() != null) {
+            populateVlanRule(
+                    instance,
+                    sport.vlanId(),
+                    nodeManager.dataPort(instance.deviceId()),
+                    true);
+        }
+        // FIXME don't add the existing instance again
+        sport.addressPairs().stream().forEach(pair -> {
+            // add instance for the additional address pairs
+            addAdditionalInstance(instance, pair.ip(), pair.mac());
+        });
+        Set<IpAddress> ipAddrs = sport.addressPairs().stream()
+                .map(AddressPair::ip).collect(Collectors.toSet());
+        populateAddressPairRule(instance, ipAddrs, true);
     }
 
     @Override
     public void instanceRemoved(Instance instance) {
-        if (instance.isNestedInstance()) {
+        if (instance.isAdditionalInstance()) {
             return;
         }
         log.info("Instance is removed {}", instance);
 
         ServiceNetwork snet = getServiceNetwork(instance);
         populateDefaultRules(instance, snet, false);
+
+        // FIXME service port might be already removed
+        ServicePort sport = getServicePort(instance);
+        if (sport.vlanId() != null) {
+            populateVlanRule(
+                    instance,
+                    sport.vlanId(),
+                    nodeManager.dataPort(instance.deviceId()),
+                    false);
+        }
+        boolean isOriginalInstance = !instance.isAdditionalInstance();
+        Set<IpAddress> ipAddrs = sport.addressPairs().stream()
+                .map(AddressPair::ip).collect(Collectors.toSet());
+        populateAddressPairRule(
+                instance,
+                isOriginalInstance ? ImmutableSet.of() : ipAddrs,
+                false);
+    }
+
+    @Override
+    public void instanceUpdated(Instance instance) {
+        if (!instance.isAdditionalInstance()) {
+            ServicePort sport = getServicePort(instance);
+            Set<MacAddress> macAddrs = sport.addressPairs().stream()
+                    .map(AddressPair::mac)
+                    .collect(Collectors.toSet());
+            hostService.getConnectedHosts(instance.host().location()).stream()
+                    .filter(h -> !h.mac().equals(instance.mac()))
+                    .filter(h -> !macAddrs.contains(h.mac()))
+                    .forEach(h -> instanceService.removeInstance(h.id()));
+        }
+        instanceDetected(instance);
+    }
+
+    private void addAdditionalInstance(Instance instance, IpAddress ip, MacAddress mac) {
+        HostId hostId = HostId.hostId(mac);
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
+                .set(Instance.NETWORK_TYPE, instance.netType().toString())
+                .set(Instance.NETWORK_ID, instance.netId().id())
+                .set(Instance.PORT_ID, instance.portId().id())
+                .set(Instance.ORIGINAL_HOST_ID, instance.host().id().toString())
+                .set(Instance.CREATE_TIME, String.valueOf(System.currentTimeMillis()));
+
+        HostDescription hostDesc = new DefaultHostDescription(
+                mac,
+                VlanId.NONE,
+                instance.host().location(),
+                Sets.newHashSet(ip),
+                annotations.build());
+
+        instanceService.addInstance(hostId, hostDesc);
     }
 
     private void populateDefaultRules(Instance instance, ServiceNetwork snet, boolean install) {
@@ -277,4 +376,128 @@
             pipeline.processFlowRule(install, flowRuleDirect);
         });
     }
+
+    private void populateVlanRule(Instance instance, VlanId vlanId, PortNumber dataPort,
+                                  boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchInPort(dataPort)
+                .matchVlanId(vlanId)
+                .matchEthDst(instance.mac())
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(instance.portNumber())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(CordVtnPipeline.TABLE_VLAN)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchInPort(instance.portNumber())
+                .matchVlanId(vlanId)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(dataPort)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
+                .forDevice(instance.deviceId())
+                .forTable(CordVtnPipeline.TABLE_VLAN)
+                .makePermanent()
+                .build();
+
+        pipeline.processFlowRule(install, flowRule);
+    }
+
+    private void populateAddressPairRule(Instance instance, Set<IpAddress> ipAddrs,
+                                         boolean install) {
+        // for traffic coming from WAN, tag 500 and take through the vSG VM
+        // based on destination ip
+        ipAddrs.stream().forEach(wanIp -> {
+            // for traffic coming from WAN, tag 500 and take through the vSG VM
+            TrafficSelector downstream = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(wanIp.toIpPrefix())
+                    .build();
+
+            TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
+                    .pushVlan()
+                    .setVlanId(CordVtnPipeline.VLAN_WAN)
+                    .setEthDst(instance.mac())
+                    .setOutput(instance.portNumber())
+                    .build();
+
+            FlowRule downstreamFlowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(downstream)
+                    .withTreatment(downstreamTreatment)
+                    .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
+                    .forDevice(instance.deviceId())
+                    .forTable(CordVtnPipeline.TABLE_DST)
+                    .makePermanent()
+                    .build();
+
+            pipeline.processFlowRule(install, downstreamFlowRule);
+        });
+
+        // remove downstream flow rules for the vSG not shown in vsgWanIps
+        for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
+            if (!rule.deviceId().equals(instance.deviceId())) {
+                continue;
+            }
+            PortNumber output = getOutputFromTreatment(rule);
+            if (output == null || !output.equals(instance.portNumber()) ||
+                    !isVlanPushFromTreatment(rule)) {
+                continue;
+            }
+
+            IpPrefix dstIp = getDstIpFromSelector(rule);
+            if (dstIp != null && !ipAddrs.contains(dstIp.address())) {
+                pipeline.processFlowRule(false, rule);
+            }
+        }
+    }
+
+    private PortNumber getOutputFromTreatment(FlowRule flowRule) {
+        Instruction instruction = flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof Instructions.OutputInstruction)
+                .findFirst()
+                .orElse(null);
+        if (instruction == null) {
+            return null;
+        }
+        return ((Instructions.OutputInstruction) instruction).port();
+    }
+
+    private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
+        Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
+        if (criterion != null && criterion instanceof IPCriterion) {
+            IPCriterion ip = (IPCriterion) criterion;
+            return ip.ip();
+        } else {
+            return null;
+        }
+    }
+
+    private boolean isVlanPushFromTreatment(FlowRule flowRule) {
+        return flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof L2ModificationInstruction)
+                .filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
+                .findAny()
+                .isPresent();
+    }
 }
diff --git a/src/main/java/org/opencord/cordvtn/impl/handler/VsgInstanceHandler.java b/src/main/java/org/opencord/cordvtn/impl/handler/VsgInstanceHandler.java
deleted file mode 100644
index f01b0e3..0000000
--- a/src/main/java/org/opencord/cordvtn/impl/handler/VsgInstanceHandler.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * 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.opencord.cordvtn.impl.handler;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onlab.packet.Ethernet;
-import org.onlab.packet.IpAddress;
-import org.onlab.packet.IpPrefix;
-import org.onlab.packet.MacAddress;
-import org.onlab.packet.VlanId;
-import org.onosproject.net.DefaultAnnotations;
-import org.onosproject.net.Host;
-import org.onosproject.net.HostId;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.flow.DefaultFlowRule;
-import org.onosproject.net.flow.DefaultTrafficSelector;
-import org.onosproject.net.flow.DefaultTrafficTreatment;
-import org.onosproject.net.flow.FlowRule;
-import org.onosproject.net.flow.FlowRuleService;
-import org.onosproject.net.flow.TrafficSelector;
-import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.flow.criteria.Criterion;
-import org.onosproject.net.flow.criteria.IPCriterion;
-import org.onosproject.net.flow.instructions.Instruction;
-import org.onosproject.net.flow.instructions.Instructions;
-import org.onosproject.net.flow.instructions.L2ModificationInstruction;
-import org.onosproject.net.host.DefaultHostDescription;
-import org.onosproject.net.host.HostDescription;
-import org.opencord.cordvtn.api.core.Instance;
-import org.opencord.cordvtn.api.core.InstanceHandler;
-import org.opencord.cordvtn.api.core.InstanceService;
-import org.opencord.cordvtn.api.net.AddressPair;
-import org.opencord.cordvtn.api.net.ServicePort;
-import org.opencord.cordvtn.impl.CordVtnNodeManager;
-import org.opencord.cordvtn.impl.CordVtnPipeline;
-
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
-import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
-import static org.opencord.cordvtn.api.net.ServiceNetwork.NetworkType.VSG;
-
-/**
- * Provides network connectivity for vSG instances.
- */
-@Component(immediate = true)
-public final class VsgInstanceHandler extends AbstractInstanceHandler implements InstanceHandler {
-
-    private static final String STAG = "stag";
-    private static final String VSG_VM = "vsgVm";
-    private static final String ERR_VSG_VM = "vSG VM does not exist for %s";
-    private static final String MSG_VSG_VM = "vSG VM %s: %s";
-    private static final String MSG_VSG_CONTAINER = "vSG container %s: %s";
-    private static final String MSG_DETECTED = "detected";
-    private static final String MSG_REMOVED = "removed";
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CordVtnPipeline pipeline;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected CordVtnNodeManager nodeManager;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected FlowRuleService flowRuleService;
-
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected InstanceService instanceService;
-
-    @Activate
-    protected void activate() {
-        netTypes = ImmutableSet.of(VSG);
-        super.activate();
-    }
-
-    @Deactivate
-    protected void deactivate() {
-        super.deactivate();
-    }
-
-    @Override
-    public void instanceDetected(Instance instance) {
-        if (isVsgContainer(instance)) {
-            log.info(String.format(MSG_VSG_CONTAINER, MSG_DETECTED, instance));
-            Instance vsgVm = getVsgVm(instance);
-            if (vsgVm == null) {
-                final String error = String.format(ERR_VSG_VM, instance);
-                throw new IllegalStateException(error);
-            }
-
-            ServicePort sport = getServicePort(vsgVm);
-            Set<IpAddress> wanIps = sport.addressPairs().stream()
-                    .map(AddressPair::ip).collect(Collectors.toSet());
-            populateVsgRules(
-                    vsgVm, sport.vlanId(),
-                    nodeManager.dataPort(vsgVm.deviceId()),
-                    wanIps, true);
-        } else {
-            log.info(String.format(MSG_VSG_VM, MSG_DETECTED, instance));
-            ServicePort sport = snetService.servicePort(instance.portId());
-            if (sport == null || sport.vlanId() == null) {
-                // service port can be updated after instance is created
-                return;
-            }
-
-            // insert vSG containers inside the vSG VM as a host
-            sport.addressPairs().stream().forEach(pair -> addVsgContainer(
-                    instance,
-                    pair.ip(),
-                    pair.mac(),
-                    sport.vlanId()));
-        }
-    }
-
-    @Override
-    public void instanceUpdated(Instance instance) {
-        if (!isVsgContainer(instance)) {
-            Set<MacAddress> vsgMacs = getServicePort(instance).addressPairs().stream()
-                    .map(AddressPair::mac)
-                    .collect(Collectors.toSet());
-            hostService.getConnectedHosts(instance.host().location()).stream()
-                    .filter(h -> !h.mac().equals(instance.mac()))
-                    .filter(h -> !vsgMacs.contains(h.mac()))
-                    .forEach(h -> instanceService.removeNestedInstance(h.id()));
-        }
-        instanceDetected(instance);
-    }
-
-    @Override
-    public void instanceRemoved(Instance instance) {
-        boolean isVsgContainer = isVsgContainer(instance);
-        log.info(String.format(
-                isVsgContainer ? MSG_VSG_CONTAINER : MSG_VSG_VM,
-                MSG_REMOVED, instance));
-
-        Instance vsgVm = isVsgContainer ? getVsgVm(instance) : instance;
-        if (vsgVm == null) {
-            // the rules are removed when VM is removed, do nothing
-            return;
-        }
-
-        // FIXME service port can be removed already
-        ServicePort sport = getServicePort(instance);
-        Set<IpAddress> wanIps = sport.addressPairs().stream()
-                .map(AddressPair::ip).collect(Collectors.toSet());
-        populateVsgRules(
-                vsgVm, sport.vlanId(),
-                nodeManager.dataPort(vsgVm.deviceId()),
-                isVsgContainer ? wanIps : ImmutableSet.of(),
-                false);
-    }
-
-    @Override
-    protected ServicePort getServicePort(Instance instance) {
-        ServicePort sport = snetService.servicePort(instance.portId());
-        if (sport == null || sport.vlanId() == null) {
-            final String error = String.format(ERR_VTN_PORT, instance);
-            throw new IllegalStateException(error);
-        }
-        return sport;
-    }
-
-    private boolean isVsgContainer(Instance instance) {
-        return  !Strings.isNullOrEmpty(instance.getAnnotation(STAG)) &&
-                !Strings.isNullOrEmpty(instance.getAnnotation(VSG_VM));
-    }
-
-    private Instance getVsgVm(Instance vsgContainer) {
-        String vsgVmId = vsgContainer.getAnnotation(VSG_VM);
-        Host host = hostService.getHost(HostId.hostId(vsgVmId));
-        if (host == null) {
-            return null;
-        }
-        return Instance.of(host);
-    }
-
-    private void addVsgContainer(Instance vsgVm, IpAddress vsgWanIp, MacAddress vsgMac,
-                                 VlanId stag) {
-        HostId hostId = HostId.hostId(vsgMac);
-        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
-                .set(Instance.NETWORK_TYPE, vsgVm.netType().toString())
-                .set(Instance.NETWORK_ID, vsgVm.netId().id())
-                .set(Instance.PORT_ID, vsgVm.portId().id())
-                .set(STAG, stag.toString())
-                .set(VSG_VM, vsgVm.host().id().toString())
-                .set(Instance.CREATE_TIME, String.valueOf(System.currentTimeMillis()));
-
-        HostDescription hostDesc = new DefaultHostDescription(
-                vsgMac,
-                VlanId.NONE,
-                vsgVm.host().location(),
-                Sets.newHashSet(vsgWanIp),
-                annotations.build());
-
-        instanceService.addNestedInstance(hostId, hostDesc);
-    }
-
-    private void populateVsgRules(Instance vsgVm, VlanId stag, PortNumber dataPort,
-                                  Set<IpAddress> vsgWanIps, boolean install) {
-        // for traffics with s-tag, strip the tag and take through the vSG VM
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchInPort(dataPort)
-                .matchVlanId(stag)
-                .build();
-
-        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                .setOutput(vsgVm.portNumber())
-                .build();
-
-        FlowRule flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
-                .forDevice(vsgVm.deviceId())
-                .forTable(CordVtnPipeline.TABLE_VLAN)
-                .makePermanent()
-                .build();
-
-        pipeline.processFlowRule(install, flowRule);
-
-        // for traffics with customer vlan, tag with the service vlan based on input port with
-        // lower priority to avoid conflict with WAN tag
-        selector = DefaultTrafficSelector.builder()
-                .matchInPort(vsgVm.portNumber())
-                .matchVlanId(stag)
-                .build();
-
-        treatment = DefaultTrafficTreatment.builder()
-                .setOutput(dataPort)
-                .build();
-
-        flowRule = DefaultFlowRule.builder()
-                .fromApp(appId)
-                .withSelector(selector)
-                .withTreatment(treatment)
-                .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
-                .forDevice(vsgVm.deviceId())
-                .forTable(CordVtnPipeline.TABLE_VLAN)
-                .makePermanent()
-                .build();
-
-        pipeline.processFlowRule(install, flowRule);
-
-        // for traffic coming from WAN, tag 500 and take through the vSG VM
-        // based on destination ip
-        vsgWanIps.stream().forEach(wanIp -> {
-            // for traffic coming from WAN, tag 500 and take through the vSG VM
-            TrafficSelector downstream = DefaultTrafficSelector.builder()
-                    .matchEthType(Ethernet.TYPE_IPV4)
-                    .matchIPDst(wanIp.toIpPrefix())
-                    .build();
-
-            TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
-                    .pushVlan()
-                    .setVlanId(CordVtnPipeline.VLAN_WAN)
-                    .setEthDst(vsgVm.mac())
-                    .setOutput(vsgVm.portNumber())
-                    .build();
-
-            FlowRule downstreamFlowRule = DefaultFlowRule.builder()
-                    .fromApp(appId)
-                    .withSelector(downstream)
-                    .withTreatment(downstreamTreatment)
-                    .withPriority(CordVtnPipeline.PRIORITY_DEFAULT)
-                    .forDevice(vsgVm.deviceId())
-                    .forTable(CordVtnPipeline.TABLE_DST)
-                    .makePermanent()
-                    .build();
-
-            pipeline.processFlowRule(install, downstreamFlowRule);
-        });
-
-        // remove downstream flow rules for the vSG not shown in vsgWanIps
-        for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
-            if (!rule.deviceId().equals(vsgVm.deviceId())) {
-                continue;
-            }
-            PortNumber output = getOutputFromTreatment(rule);
-            if (output == null || !output.equals(vsgVm.portNumber()) ||
-                    !isVlanPushFromTreatment(rule)) {
-                continue;
-            }
-
-            IpPrefix dstIp = getDstIpFromSelector(rule);
-            if (dstIp != null && !vsgWanIps.contains(dstIp.address())) {
-                pipeline.processFlowRule(false, rule);
-            }
-        }
-    }
-
-    private PortNumber getOutputFromTreatment(FlowRule flowRule) {
-        Instruction instruction = flowRule.treatment().allInstructions().stream()
-                .filter(inst -> inst instanceof Instructions.OutputInstruction)
-                .findFirst()
-                .orElse(null);
-        if (instruction == null) {
-            return null;
-        }
-        return ((Instructions.OutputInstruction) instruction).port();
-    }
-
-    private IpPrefix getDstIpFromSelector(FlowRule flowRule) {
-        Criterion criterion = flowRule.selector().getCriterion(IPV4_DST);
-        if (criterion != null && criterion instanceof IPCriterion) {
-            IPCriterion ip = (IPCriterion) criterion;
-            return ip.ip();
-        } else {
-            return null;
-        }
-    }
-
-    private boolean isVlanPushFromTreatment(FlowRule flowRule) {
-        return flowRule.treatment().allInstructions().stream()
-                .filter(inst -> inst instanceof L2ModificationInstruction)
-                .filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
-                .findAny()
-                .isPresent();
-    }
-}